1

私はソフトウェアの世界から来て、VHDL でシーケンシャル アルゴリズムをコーディングする方法を見つけようとしています。教科書によると、プロセス内のステートメントは順次実行されます。しかし、シグナルではなく変数に関してのみ真実であることに気付きました。プロセス内のシグナルに関して、それらはプロセスの最後に更新され、評価は右オペランドの前の値を使用しています。私の理解では、それはまだ並行しています。パフォーマンス上の理由から、複雑な計算に常に変数を使用できるとは限りません。

  1. しかし、信号を使用してシーケンシャル アルゴリズムを提示するにはどうすればよいでしょうか。私の最初の考えは、FSM を使用することです。本当?FSM は、VHDL で順次アルゴリズムを適切にコーディングする唯一の方法ですか?
  2. プロセス内のシグナル ステートメントが一種の同時実行であると私が正しければ、これとアーキテクチャ レベルでのシグナルの同時割り当てとの違いは何ですか? プロセスのシーケンシャルな性質は、変数の割り当てにのみ適用されますか?
4

2 に答える 2

4

アルゴリズムのステップを異なるサイクルで実行しようとしているときに、プロセス内の「順次」構造はそれ自体では実行できないことに気付きました。実際、変数は役に立ちません。シーケンシャル プログラムは、明示的な "wait for some_event" を使用しない限り (たとえば、wait for raise_edge(clk))、展開され、1 クロック サイクルで実行されます。

おそらく変数を使用していることに気付いたように、これはかなり長いクロック サイクルである可能性があります。

VHDL で実行をシーケンシャル化するには、主に 3 つの方法があり、それぞれ目的が異なります。

a と b の間の線形補間を実装してみましょう。

a, b, c, x : unsigned(15 downto 0);
x <= ((a * (65536 - c)) + (b * c)) / 65536;

(1) は古典的なステート マシンです。最良の形態は単一プロセス SM です。ここで、計算はいくつかのサイクルに分割され、一度に多くても 1 つの乗算が進行することを保証します (乗算器は高価です!) が、C1 は並列で計算されます (加算/減算は安価です!)。中間結果のシグナルの代わりに変数で安全に書き直すことができます。

type state_type is (idle, step_1, step_2, done);
signal state     : state_type := idle;
signal start     : boolean := false;
signal c1        : unsigned(16 downto 0); -- range includes 65536!
signal p0, p1, s : unsigned(31 downto 0);

process(clk) is
begin
   if rising_edge(clk) then
      case state is
      when idle   => if start then
                        p1    <= b * c;
                        c1    <= 65536 - c;
                        state <= step_1;
                     end if;
      when step_1 => P0 <= a * c1;
                     state <= step_2;
      when step_2 => s <= p0 + p1;
                     state <= done;
      when done   => x <= s(31 downto 16);
                     if not start then  -- avoid retriggering
                        state <= idle;  
                     end if;
      end case;
   end if;
end process;

(2) は、Martin Thompson によってリンクされた「暗黙のステート マシン」です (優れた記事です!) ... Martin の回答が消えたため、リンクを追加するために編集されました。明示的なステート マシンの場合と同じ注意事項が適用されます。

process(clk) is
begin
   if start then
      p1 <= b * c;
      c1 <= 65536 - c;
      wait for rising_edge(clk);
      p0 <= a * c1;
      wait for rising_edge(clk);
      s  <= p0 + p1;
      wait for rising_edge(clk);
      x  <= s(31 downto 16);
      while start loop
         wait for rising_edge(clk);
      end loop;
   end if;
end process;

(3) はパイプライン化されたプロセッサです。ここでは、実行に数サイクルかかりますが、すべてが並行して行われます! パイプラインの深さ (サイクル単位) により、論理的に連続する各ステップを連続して実行できます。これにより、計算の長いチェーンがサイクルサイズのステップに分割されるため、高いパフォーマンスが可能になります...

    signal start     : boolean := false;
    signal c1        : unsigned(16 downto 0); -- range includes 65536!
    signal pa, pb, pb2, s : unsigned(31 downto 0);
    signal a1        : unsigned(15 downto 0);

process(clk) is
begin
   if rising_edge(clk) then
      -- first cycle
      pb <= b * c;
      c1 <= 65536 - c;
      a1 <= a;     -- save copy of a for next cycle
      -- second cycle
      pa <= a1 * c1;  -- NB this is the LAST cycle copy of c1 not the new one!
      pb2 <= pb;   -- save copy of product b
      -- third cycle
      s  <= pa + pb2;
      -- fourth cycle
      x  <= s(31 downto 16);
   end if;
end process;

ここでは、リソースは共有されません。各クロック サイクルで 2 つの乗算があるため、2 つの乗算器を使用します。また、中間結果とコピーのためにさらに多くのレジスタを使用します。ただし、すべてのサイクルで a、b、c の新しい値を指定すると、サイクルごとに新しい結果が出力されます。つまり、入力から 4 サイクル遅れます。

于 2013-02-05T16:51:26.797 に答える
1
  1. ほとんどのマルチサイクル アルゴリズムは、提案されているように FSM を使用するか、パイプライン ロジックを使用して実装できます。アルゴリズムが厳密に連続したステップで構成されている (つまり、ループがない) 場合は、パイプライン化されたロジックがおそらくより適切な選択です。FSM は通常、入力に応じて異なる制御フローを必要とするより複雑なアルゴリズムにのみ使用されます。

    パイプライン化されたロジックは、事実上、レジスタを使用して複数の「ステージ」に分割された組み合わせロジックの非常に長いチェーンであり、データは 1 つのステージから次のステージに流れます。レジスターは、各ステージ (2 つのレジスター間) の遅延を減らすために追加され、遅延の増加を犠牲にしてより高いクロック周波数を可能にします。ただし、前のデータ項目が完了する前に新しいデータの処理が開始される可能性があるため、待ち時間が長くなってもスループットが低下するわけではないことに注意してください。これは一般に、FSM では不可能です。

  2. アーキテクチャとは対照的に、プロセス内のシグナル割り当ての最大の違いは、プロセス内の複数の場所でシグナルに値を割り当てることができ、最後の割り当てが「勝利」することです。アーキテクチャ レベルでは、信号への割り当てステートメントは 1 つだけ可能です。多くの制御フロー ステートメント (if、case/when など) も、アーキテクチャ レベルではなく、プロセス内でのみ使用できます。

于 2013-02-04T16:56:27.127 に答える