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


9

背景

这是一个个人项目;考虑到将FPGA连接到N64,FPGA接收的字节值然后通过UART发送到我的计算机。它实际上运行得很好!不幸的是,在随机时间,设备将发生故障,然后恢复。通过调试,我设法找到了问题,但是由于无法使用VHDL,我对如何解决它感到困惑。

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

问题

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

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

这是正确的行为: 在此处输入图片说明

FPGA是蓝色波形,橙色波形是N64的输入。在接收期间,我的FPGA“回音”输入的信号以进行调试。FPGA计数到9后,它开始通过UART传输数据。请注意,N64完成后,数字引脚计数为9,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;

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

同时,我将继续使用它(最终会得到它)!帮助我Stack Exchange,您是我唯一的希望!

编辑

在调试中的另一个发现是,状态将从waitForStart跳回到waitForStop。我给每个状态赋予一个值,其中waitForStart等于“ 5”,而waitForStop等于“ 4”。见下图: 在此处输入图片说明


1
在您的第一种情况下,行“ s_bitCount <= X“ 0”;“ 那是X的错字吗?
travisbartley

@ trav1s否,“ X”表示十六进制。因此,X“ 0”实际上是二进制的“ 0000”。
Nick Williams

1
我在linter上运行代码时遇到了一些错误。信号N64RXD和TDRE不应在连续过程的灵敏度列表中使用,管线36
travisbartley

1
@ trav1s感谢您的指针,我删除了这些参数;没错,这些不是必需的。不幸的是我仍然有这个问题。通过示波器,我添加了信号以检测我处于哪种状态。由于某种原因,FPGA从“ waitForStart”跳回到“ waitForStop”,而两者之间没有任何状态!这就是为什么它不进行计数的原因,因为FPGA尚未达到对位进行计数的状态。“跳回”似乎是问题所在。
尼克·威廉姆斯

1
但是“ waitForStart”->“ waitForStop”过渡无效。没有办法在一个周期内实现跳跃。仔细检查以确保两者之间没有非常短暂的状态。否则,必须存在硬件/时序故障。
travisbartley

Answers:


9

我在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上写了该页面。我刚刚意识到,我上次更新该站点时断开了链接,并将在早上回到计算机时进行修复。我不知道有多少人使用该页面!)


感谢您的出色而全面的回答!我将尝试一下,使我的机器更坚固。
Nick Williams

2
实际上,它与亚稳定性几乎没有关系(尽管这也是一个问题),并且与从异步输入到保存状态变量位的各种FF的路径延迟不同有关。
Dave Tweed

你是对的,@ DaveTweed; 我倾向于将两者融合在一起,这是错误的想法。
akohlsmith 2013年

我已经编辑了答案,以考虑@DaveTweed的评论。
akohlsmith

1
@akohlsmith太棒了!我添加了同步器,这就是解决方案。另外,您编写了混合页面,这是一个令人难以置信的巧合。我在参考该文章的N64协议上找到了大量资源,但令我失望的是链接断开了。感谢您修复它。
Nick Williams

6

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

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

最后一条语句是为什么您还应该when others => ...在状态机用例中始终使用默认案例(在VHDL中为),该默认案例将您从任何非法状态转移到合法状态。


是的,这就是我要隔离的结论,但是在获得足够的信息之前我不想跳到这个结论……
travisbartley 2013年

1
该死,你击败了我。我将此归咎于我在平板电脑上输入的所有内容。:-)
akohlsmith

@akohlsmith,成为东方最快的枪支并不是唯一要回答的问题。您的答案很有用,并且显然没有作弊,因为您在此答案发布后不久就发布了。
travisbartley

我曾经以为这when others =>是有帮助的,但是事实证明,除非您添加属性以确保合成器了解您想要“安全”状态机,否则它并不能使您声明(在我使用的任何合成器下)。正常行为是优化为一站式表示,而不提供恢复逻辑。例如,请参见xilinx.com/support/answers/40093.htmlsynopsys.com/Company/Publications/SynopsysInsight/Pages/…
马丁·汤普森

哇!这是一个很棒的技巧,它就像一个魅力。
Nick Williams
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.