VHDL:ビットのカウント時に受信モジュールがランダムに失敗する


9

バックグラウンド

これは個人的なプロジェクトです。FPGAをN64に接続すると、FPGAが受信したバイト値はUARTを介してコンピューターに送信されます。それは実際にはかなりうまく機能します!不幸にも不定期に、デバイスは失敗し、その後回復します。デバッグによって問題を見つけることができましたが、VHDLにはかなりの能力がないため、修正方法に困惑しています。

私は2日間VHDLをいじっていて、これを解決できないかもしれません。

問題

FPGAへのN64信号を測定するオシロスコープがあり、他のチャネルはFPGAの出力に接続しています。カウンター値を記録するデジタルピンもあります。

基本的に、N64はSTOPビットを含む9つのデータビットを送信します。カウンターは受信したデータビットをカウントし、9ビットに達すると、FPGAはUARTを介して送信を開始します。

正しい動作は次のとおりです。 ここに画像の説明を入力してください

FPGAは青色の波形で、オレンジ色の波形はN64の入力です。受信中、私のFPGAはデバッグの目的で入力の信号を "エコー"します。FPGAが9までカウントした後、UARTを介してデータの送信を開始します。N64が終了した直後にデジタルピンが9にカウントされ、FPGA出力がLOWになることに注意してください。

失敗の例を次に示します。

ここに画像の説明を入力してください

カウンターがビット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」の値を指定しました。下の画像をご覧ください。 ここに画像の説明を入力してください


1
最初のケースブロックには、「s_bitCount <= X "0";」という行があります。そのXはタイプミスですか?
travisbartley 2013

@ trav1sいいえ、その「X」は16進数を示します。したがって、X "0"は実際にはバイナリでは "0000"です。
Nick Williams

1
リンターを通じてコードを実行すると、いくつかのエラーが発生しました。信号N64RXDとTDREは順次処理のセンシティビティリストに使用すべきではない、ライン36
travisbartley

1
@ trav1sポインタをありがとう、私はそれらのパラメータを削除しました。あなたが正しい、それらは必要ありません。残念ながらまだ問題があります。スコープを使用して、現在の状態を検出する信号を追加しました。何らかの理由で、FPGAは「waitForStart」から「waitForStop」にジャンプし、その間に状態はありません!FPGAがビットをカウントする状態に達しないため、カウントされないのはこのためです。「ジャンプバック」が問題のようです。
Nick Williams

1
ただし、 "waitForStart"-> "waitForStop"遷移は無効です。単一のサイクルでそのジャンプを行う方法はありません。間に非常に短い状態がないことを確認するために、非常に注意深く確認してください。それ以外の場合は、ハードウェア/タイミングの障害があるはずです。
travisbartley 2013

回答:


9

rxデータラインにシンクロナイザが表示されません。

すべての非同期入力は、サンプリングクロックに同期する必要があります。これには2つの理由があります。メタスタビリティとルーティングです。これらはさまざまな問題ですが、相互に関連しています。

信号がFPGAファブリックを伝搬するまでには時間がかかります。FPGA内のクロックネットワークは、これらの「移動」遅延を補償するように設計されているため、FPGA内のすべてのフリップフロップは、まったく同じタイミングでクロックを認識します。通常のルーティングネットワークにはこれがなく、すべての信号はクロックが変化する前に少しの間安定し、クロックが変化した後も少しの間安定している必要があるという規則に依存しています。これらの小さな時間は、特定のフリップフロップのセットアップおよびホールドタイムとして知られています。ツールチェーンの配置配線コンポーネントは、特定のデバイスの配線遅延を非常によく理解しており、信号がFPGAのフリップフロップのセットアップおよびホールドタイムに違反しないという基本的な仮定を行います。

サンプリングクロックに同期していない信号がある場合、新しい値が伝播する時間がなかったため、1つのフリップフロップが信号の「古い」値を認識する状況に陥ることがあります。これで、同じ信号を見るロジックが2つの異なる値を見るという望ましくない状況に陥ります。これは、誤動作、クラッシュしたステートマシン、およびあらゆる種類の破壊を引き起こす可能性があります。

すべての入力信号を同期する必要があるもう1つの理由は、メタスタビリティと呼ばれるものです。この主題について書かれたボリュームがありますが、簡単に言えば、デジタル論理回路はその最も基本的なレベルではアナログ回路です。クロックラインが上昇すると、入力ラインの状態がキャプチャされ、その入力がその時点で安定した高レベルまたは低レベルではない場合、未知の「中間」値がサンプリングフリップフロップによってキャプチャされます。

ご存知のように、FPGAはデジタルビーストであり、ハイでもローでもない信号には反応しません。さらに悪いことに、その不確定な値がサンプリングフリップフロップを通過してFPGAに到達すると、ロジックの大部分が不確定な値を認識してそれを理解しようとするため、あらゆる種類の奇妙さを引き起こす可能性があります。

解決策は、信号を同期させることです。最も基本的なレベルでは、これはフリップフロップのチェーンを使用して入力をキャプチャすることを意味します。最初のフリップフロップでキャプチャされ、それをなんとかして作成したメタステーブルレベルは、複雑なロジックに到達する前に解決される可能性があります。入力を同期するには、通常2つのフリップフロップで十分です。

基本的なシンクロナイザは次のようになります。

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までのさまざまなパス遅延に関するすべてのことです。
デイブツイード2013

その通りです、@ DaveTweed; 私は2つをまとめる傾向があり、それは間違った考えです。
akohlsmith 2013

@DaveTweedのコメントを考慮に入れるように回答を編集しました。
akohlsmith 2013

1
@akohlsmithすごい!私はシンクロナイザーを追加し、それが解決策でした。また、それはあなたがミックスダウンページを書いたのは信じられないほどの偶然です。その記事を参照しているN64プロトコル上のリソースの束を見つけましたが、リンクが壊れていてがっかりしました。修正していただきありがとうございます。
Nick Williams

6

問題は、同期されていない信号を使用して状態マシンで決定を下していることです。これらの外部信号はすべて、ステートマシンで使用する前に、ダブルFFシンクロナイザーを介して供給する必要があります。

これは、状態変数の2つ以上のビットへの変更を含むすべての状態遷移で発生する可能性がある状態マシンの微妙な問題です。非同期入力を使用すると、ビットの1つが変更され、他のビットは変更に失敗する可能性があります。これにより、意図した状態とは異なる状態になり、合法的な状態である場合とそうでない場合があります。

最後のステートメントはwhen others => ...、ステートマシンのケースステートメントに、違法な状態から合法的な状態に移行するデフォルトのケース(VHDLの場合)を常に持つ必要がある理由です。


ええ、これは分離しようとしていた結論ですが、十分な情報を取得する前にジャンプしたくありませんでした...
travisbartley 2013

1
くそー、あなたは私をそれに負けました。私はこれをすべてタブレットでタイプすることに責任があります。:-)
akohlsmith 2013

@akohlsmith、東で最速の銃であることは答えに重要な唯一のものではありません。あなたの答えは役に立ち、あなたがこれの直後に投稿したので明らかに不正行為ではありませんでした。
travisbartley 2013

以前はそれwhen others =>が役に立っていると思っていましたが、シンセが「安全な」ステートマシンを望んでいることを確実に理解できるように属性を追加しない限り、(私が使用したどのシンセサイザーでも)主張したことは得られません。通常の動作は、ワンホット表現に最適化することであり、回復ロジックを提供しません。たとえば、xilinx.com / support / answers / 40093.htmlおよびsynopsys.com/Company/Publications/SynopsysInsight/Pages/…を参照してください。
マーティントンプソン

うわー!それは素晴らしいヒントであり、魅力のように機能しました。
Nick Williams
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.