有限状態マシンは、FPGAファームウェアの開発において非常に重要な役割を果たします。 FPGAの前でも提案されたMilesマシンとMooreマシンの2つの古典的なタイプのマシンについては誰もが耳にしたことがあります。 ただし、FPGAの構築の詳細は独自の調整を行い、作業の過程で、オートマトンを記述する非常に明確なスタイルを開発しました。
文献はオートマトンのさまざまな実装を提供します。 このような記事はHabrにもあります-cannyの
「VHDLでのデジタル自動機の説明」 ただし、FPGAでの実際の実装、特に高周波では、このような実装は不便です。 次のオプションが提案されています。
コンポーネントの説明:
entity ex_user is port ( reset : in std_logic:
タイプと信号の説明:
type stp_type is ( s0, s1, s2, s3 ); signal stp : stp_type; signal rstp0 : std_logic; signal rstp : std_logic;
同期をリセット:
rstp0 <= reset after 1 ns when rising_edge( clk ); rstp <= rstp0 after 1 ns when rising_edge( clk );
この場合、マシン自体は非常に単純です
pr_stp: process( clk ) begin if( rising_edge( clk ) ) then case( stp ) is when s0 =>
機能:
- ステータス信号の個々のタイプの説明
- 信号リセット
- リセットはステータス信号にのみ影響します
- すべての入力信号は、マシンのクロック周波数と同期する必要があります
- 遷移と出力信号は1つのプロセスで説明されます。
- 1 ns後の可用性-モデリングを容易にするため
- MooreマシンとMileマシンの両方を実装できます
ここには革命的なものは何もありません;これはすべて長い間知られています。 しかし、これはすべて一緒に記述スタイルを形成します。
説明の機能に関する詳細。 これは主に、ステータス信号用の別のタイプの導入です。 この場合、列挙型が記述されます。 これにより、シンセサイザーは信号のタイプを選択できます。 ワンホット、バイナリカウンター、またはグレーカウンターのいずれかです。 実装のタイプは、シンセサイザーディレクティブで指定できます。 これにより、説明と実装が分離されます。 誰かがこれを好まない場合は、std_logic_vector型と個々の定数s0、s1などを設定することはかなり可能です。 時々、s0、s1という名前は有益ではないと私に主張し、特定の状態に意味が関連する定数を使用する方が良いと主張します。 そのため、私もそれをやろうとしましたが、開発プロセス中に状態のロジックが非常に頻繁に変更されますが、名前は変わりません。
リセット信号の同期-多くの場合、リセットはマシンのクロック周波数に対して非同期です。 それがどこから来たかをチェックしないために、常に2つのトリガーを置く方が良いです。 いずれにしても、これはトレースを容易にします。 リセットはステータス信号にのみ影響します。 また、特に多数の出力信号でトレースを容易にしますが、これには初期状態ですべての出力信号が記述されている必要があります。
マシンは同期回路であるため、すべての入力信号はクロック周波数に同期する必要があります。ここにはオプションはありません。 信号の発信元を知る必要があります。 この信号が異なるクロック周波数で生成される場合は、2つのトリガーを設定する必要があります(リセット信号の場合も同様)。
遷移と出力信号が1つのプロセスで記述されるという事実により、開発プロセスが視覚的に簡素化され、視認性が向上します。 教科書で推奨されているように、オートマトンを2つ(または3つのプロセス)で作成する場合、一見するのは困難です。 1つのコンピューター画面上の大きなマシンが収まらないためだけの場合。
最も物議をかもす声明は、信号を割り当てるときの1ns後の記録です。 フォーラムの1つで、これについて非常に強く批判されました。 また、経験が蓄積するにつれて、私はこの悪い習慣を取り除くと書いています。 私の経験は、これが非常に良い習慣であることを示しています。 このような記録があることで、シミュレーション結果と実際の機器での作業結果が同じになることを確認できます。 デルタ遅延の概念がすべてです。
簡単な言語構成を考えてみましょう。
clk2 <= clk1; b <= a when rising_edge( clk1 ); c <= b when rising_edge( clk1 ); d <= b when rising_edge( clk2 );
作業の結果は図に示されています。
この図は、クロック信号
clk1と
clk2が一致することを視覚的に示していますが、実際には
clk2はデルタ遅延の値
だけclk1に対して遅延しています。 これは正しいです。 しかし、ここでは信号
dは信号
cと一致するはずですが、これは起こりません。 以前に動作します。 これは、
clk2周波数にスナップするという事実によるものです。 ハードウェアで作業している場合、
clk2は
clk1と完全に一致し、グローバルクロックライン上の同じ信号になります。 実際のプロジェクトでは、clk2 <= clk1のような割り当てが十分に一般的です。 もちろん、すべての開発者がこれを行うことを厳密に禁止しようとすることはできますが、その結果を強く疑います。 割り当ての代わりに、エイリアスタイプの説明を作成できます。
alias clk2 is clk1;
この場合、
clk2は別の
clk1名になり、結果は正しいものになります。 しかし、もう1つポイントがあります。 純粋に視覚的なため、タイムダイアグラムでは、クロック周波数に対して信号がいつ変化するかは明確ではありません。
そして、1 ns後に追加するとどうなるかを次に示します。
信号
cと
dは正しく形成されています。 信号
bの変化は、クロック周波数の前面に対してはっきりと見えます。
かつて、何年も前に、モデルと実際の機器のさまざまな動作の原因を探すのに多くの時間を費やしました。 シフトはたった1ビートでした。 そしてこれがまさに理由です-クロック周波数の割り当てと1 ns後の不在。 それ以来、私は常に遅延を追加し、決して後悔していません。
そして最後に、この例はムーアのオートマトンを示しています。 出力信号は状態のみに依存します。 ただし、次のようにコードはいつでも変更できます。
when s1 =>
この場合、q信号は1サイクル早く生成されます。 それはステータスと入力信号に依存します。 そして、これはすでにMilesマシンです。
この記事では、
VHDL上のデジタル自動販売機の説明も、1つのプロセスでバリアントの説明を提供します。 一見、すべてが一致しています。
1つのプロセスでのCAの説明 PROCESS (clk, reset) BEGIN IF reset = '1' THEN state <= Init; ELSIF (rising_edge(clk)) THEN CASE state IS WHEN Init => IF cnt = '1' THEN state <= R; ELSE state <= Init; END IF; output <= "000"; WHEN R => IF cnt = '1' THEN state <= RG; ELSE state <= R; END IF; output <= "100"; WHEN RG => IF cnt = '1' THEN state <= G; ELSE state <= RG; END IF; output <= "010"; WHEN G => IF cnt = '1' THEN state <= GR; ELSE state <= G; END IF; output <= "001"; WHEN GR => IF cnt = '1' THEN state <= R; ELSE state <= GR; END IF; OUTPUT <= "010"; END CASE; END IF; END PROCESS;
この説明には非常に重大な間違いがあります。
リセット信号の動作中、
OUTPUT出力信号は定義されていません;それを決定するには、リセット内で信号の目的を入力する必要があります。
IF reset = '1' THEN state <= Init; OUTPUT <= "000"; ELSIF
この場合、3つのリセット行が追加されます。 多数の出力信号があると、FPGAトレースが悪化します。
私の場合、リセット信号はステータス信号にのみ作用しますが、プロセス内のケースの後に位置しているため、言語の規則に従って、ケース内の状態の割り当てよりも優先されます。 このソリューションの特徴は、出力信号の初期状態への転送がリセット信号の2番目のクロックサイクルでのみ発生することです。 ただし、ほとんどの場合、これは重要ではありません。
結論として、このスタイルの記述は非常に確立されており、マシンはFPGAで十分に離婚していることに注意してください。 複数のマシンのカスケード接続およびFPGA内の他の回路への接続は簡単に取得できます。