VHDL:接收模块在计数位时随机失败

电器工程 FPGA 视频文件 协议
2022-02-04 00:38:00

背景

这是一个个人项目;它涉及将 FPGA 连接到 N64,然后 FPGA 接收到的字节值通过 UART 发送到我的计算机。它实际上功能非常好!不幸的是,在随机时间,设备会发生故障,然后恢复。通过调试,我设法找到了问题,但是我对如何解决它感到困惑,因为我对 VHDL 相当无能。

几天来我一直在玩弄 VHDL,我可能无法解决这个问题。

问题

我有一个示波器测量输入 FPGA 的 N64 信号,另一个通道连接到 FPGA 的输出。我也有记录计数器值的数字引脚。

本质上,N64 发送 9 个数据位,包括一个 STOP 位。计数器对接收到的数据位进行计数,当我达到 9 位时,FPGA 开始通过 UART 传输。

这是正确的行为: 在此处输入图像描述

FPGA是蓝色波形,橙色波形是N64的输入。在接收期间,我的 FPGA “回声”输入信号以进行调试。FPGA 计数到 9 后,开始通过 UART 传输数据。请注意,数字引脚数为 9,并且在 N64 完成后,FPGA 输出立即变为低电平。

下面是一个失败的例子:

在此处输入图像描述

请注意,计数器跳过了第 2 位和第 7 位!FPGA 到达终点,等待 N64 的下一个起始位,但什么也没有。所以FPGA超时并恢复。

这是 N64 接收模块的 VHDL。它包含计数器:s_bitCount。

library IEEE;
use IEEE.STD_LOGIC_1164.all;   
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity N64RX is
     port(
         N64RXD : in STD_LOGIC;                    --Data input
         clk25 : in STD_LOGIC;
         clr : in STD_LOGIC; 
         tdre : in STD_LOGIC;                      --detects when UART is ready
         transmit : out STD_LOGIC;                 --Signal to UART to transmit  
         sel : out STD_LOGIC; 
         echoSig : out STD_LOGIC;
         bitcount : out STD_LOGIC_VECTOR(3 downto 0);
         data : out STD_LOGIC_VECTOR(3 downto 0)   --The significant nibble
         );
end N64RX;

--}} End of automatically maintained section

architecture N64RX of N64RX is 

type state_type is (start, delay2us, sigSample, waitForStop, waitForStart, timeout, count9bits, sendToUART);

signal state: state_type;
signal s_sel, s_echoSig, s_timeoutDetect : STD_LOGIC;
signal s_baudCount : STD_LOGIC_VECTOR(6 downto 0);  --Counting variable for baud rate in delay
signal s_bitCount : STD_LOGIC_VECTOR(3 downto 0);  --Counting variable for number of bits recieved 
signal s_data : STD_LOGIC_VECTOR(8 downto 0);   --Signal for data

constant delay : STD_LOGIC_VECTOR(6 downto 0) := "0110010";  --Provided 25MHz, 50 cycles is 2us 
constant delayLong : STD_LOGIC_VECTOR(6 downto 0) := "1100100";

begin 

n64RX: process(clk25, N64RXD, clr, tdre)
begin
    if clr = '1' then
        s_timeoutDetect <= '0';
        s_echoSig <= '1';
        s_sel <= '0';
        state <= start;
        s_data <= "000000000";
        transmit <= '0'; 
        s_bitCount <= "0000";
        s_baudCount <= "0000000";  
    elsif (clk25'event and clk25 = '1') then    --on rising edge of clock input
        case state is
            when start =>   
                --s_timeoutDetect <= '0';
                s_sel <= '0';
                transmit <= '0';        --Don't request UART to transfer   
                s_data <= "000000000";
                s_bitCount <= X"0";   
                if N64RXD = '1' then
                    state <= start;
                elsif N64RXD = '0' then     --if Start bit detected
                    state <= delay2us;
                end if;    

            when delay2us =>                 --wait two microseconds to sample
                --s_timeoutDetect <= '0';
                s_sel <= '1';
                s_echoSig <= '0';
                if s_baudCount >= delay then    
                    state <= sigSample;
                else
                    s_baudCount <= s_baudCount + 1;
                    state <= delay2us;
                end if;  

            when sigSample => 
                --s_timeoutDetect <= '1';
                s_echoSig <= N64RXD;
                s_bitCount <= s_bitCount + 1;
                s_baudcount <= "0000000";
                s_data <= s_data(7 downto 0) & N64RXD;      
                state <= waitForStop;   

            when waitForStop => 
                s_echoSig <= N64RXD;
                if N64RXD = '0' then
                    state <= waitForStop;
                elsif N64RXD = '1' then
                    state <= waitForStart;
                end if;   

            when waitForStart => 
                s_echoSig <= '1';
                s_baudCount <= s_baudCount + 1; 
                if N64RXD = '0' then 
                    s_baudCount <= "0000000";
                    state <= delay2us;
                elsif N64RXD = '1' then 
                    if s_baudCount >= delayLong then
                        state <= timeout;
                    elsif s_bitCount >= X"9" then
                        state <= count9bits;
                    else
                        state <= waitForStart;
                    end if;
                end if;     

            when count9bits =>  
                s_sel <= '0';
                if tdre = '0' then
                    state <= count9bits;
                elsif tdre = '1' then
                    state <= sendToUART;
                end if;   

            when sendToUART =>
                transmit <= '1';
                if tdre = '0' then
                    state <= start;
                else
                    state <= sendToUART;
                end if;

            when timeout =>
                --s_timeoutDetect <= '1';
                state <= start;

        end case;   
    end if;
end process n64RX;  
--timeoutDetect <= s_timeoutDetect;
bitcount <= s_bitCount;
echoSig <= s_echoSig;
sel <= s_sel;
data <= s_data(4 downto 1);

end N64RX;

那么,有什么想法吗?调试提示?编码有限状态机的技巧?

与此同时,我会继续玩它(我最终会拥有它)!帮我堆栈交换,你是我唯一的希望!

编辑

在我的调试中进一步发现,状态会从waitForStart跳回到waitForStop。我给每个状态一个值,waitForStart 等于“5”,waitForStop 等于“4”。见下图: 在此处输入图像描述

2个回答

我在 rx 数据线上没有看到同步器。

所有异步输入必须与采样时钟同步。这有几个原因:亚稳态和路由。这些是不同的问题,但又是相互关联的。

信号通过 FPGA 架构传播需要时间。FPGA 内的时钟网络旨在补偿这些“旅行”延迟,以便 FPGA 内的所有触发器在完全相同的时刻看到时钟。普通的路由网络没有这个,而是依赖于所有信号必须在时钟改变之前稳定一段时间,在时钟改变之后保持稳定一段时间的规则。这些小时间被称为给定触发器的建立和保持时间。工具链的布局和布线组件对特定器件的布线延迟有很好的理解,并做出了一个基本假设,即信号不违反 FPGA 中触发器的建立和保持时间。

当您的信号与采样时钟不同步时,您最终可能会遇到这样的情况:一个触发器看到信号的“旧”值,因为新值没有时间传播。现在您处于不利的情况,即查看同一信号的逻辑会看到两个不同的值。这可能导致错误操作、崩溃状态机和各种难以诊断的破坏。

必须同步所有输入信号的另一个原因是所谓的亚稳态。关于这个主题有很多著作,但简而言之,数字逻辑电路在最基本的层面上是模拟电路。当您的时钟线上升时,会捕获输入线的状态,并且如果该输入当时不是稳定的高电平或低电平,则采样触发器可以捕获未知的“中间”值。

如您所知,FPGA 是数字野兽,对既不高也不低的信号反应不佳。更糟糕的是,如果该不确定值通过采样触发器并进入 FPGA,则可能会导致各种奇怪现象,因为大部分逻辑现在会看到一个不确定值并试图理解它。

解决方案是同步信号。在最基本的层面上,这意味着您使用一连串触发器来捕获输入。任何可能已被第一个触发器捕获并成功完成的亚稳态级别都有另一个机会在它遇到您的复杂逻辑之前得到解决。两个触发器通常足以同步输入。

一个基本的同步器如下所示:

entity sync_2ff is
port (
    async_in : in std_logic;
    clk : in std_logic;
    rst : in std_logic;
    sync_out : out std_logic
);
end;

architecture a of sync_2ff is
begin

signal ff1, ff2: std_logic;

-- It's nice to let the synthesizer know what you're doing. Altera's way of doing it as follows:
ATTRIBUTE altera_attribute : string;
ATTRIBUTE altera_attribute OF ff1 : signal is "-name SYNCHRONIZER_IDENTIFICATION ""FORCED IF ASYNCHRONOUS""";
ATTRIBUTE altera_attribute OF a : architecture is "-name SDC_STATEMENT ""set_false_path -to *|sync_2ff:*|ff1 """;

-- also set the 'preserve' attribute to ff1 and ff2 so the synthesis tool doesn't optimize them away
ATTRIBUTE preserve: boolean;
ATTRIBUTE preserve OF ff1: signal IS true;
ATTRIBUTE preserve OF ff2: signal IS true;

synchronizer: process(clk, rst)
begin
if rst = '1' then
    ff1 <= '0';
    ff2 <= '0';
else if rising_edge(clk) then
    ff1 <= async_in;
    ff2 <= ff1;
    sync_out <= ff2;
end if;
end process synchronizer;
end sync_2ff;

将 N64 控制器的 rx 数据线的物理引脚连接到同步器的 async_in 输入,并将 sync_out 信号连接到 UART 的 rxd 输入。

不同步的信号会导致奇怪的问题。确保与读取信号的进程时钟不同步的任何连接到 FPGA 元件的输入是同步的。这包括按钮、UART“rx”和“cts”信号……任何与 FPGA 用于采样信号的时钟不同步的东西。

(旁白:多年前,我在www.mixdown.ca/n64dev上写了这个页面。我刚刚意识到我上次更新网站时断开了链接,并会在早上回到电脑前修复它。我不知道有这么多人使用该页面!)

您的问题是您正在使用非同步信号在状态机中做出决策。在状态机中使用它们之前,您应该通过双 FF 同步器提供所有这些外部信号。

这是状态机的一个微妙问题,可能出现在任何涉及状态变量中两个或多个位的更改的状态转换中。如果您使用非同步输入,则其中一位可能会更改,而另一位则无法更改。这会将您带到与预期不同的状态,它可能是也可能不是合法状态。

最后一条语句是为什么您还应该when others => ...在状态机 case 语句中始终有一个默认情况(在 VHDL 中),它将您从任何非法状态带入合法状态。