プログラマー向けFPGA、簡単なレシピ

優先コード構造



電子デバイスの開発において、回路設計者とソフトウェア開発者の間の境界線は非常に曖昧です。 FPGAで誰がRTLを書くべきかについて実際に語る内容。







一方では、RTLは回路の領域であり、他方では、FPGAリソースがより安くなり、シンセサイザーがよりスマートになります。 FPGAに対するRTLデザイナーのエラーの価格はプログラマーのエラーの価格を超えず、作成された回路は、通常のプロセッサフ​​ァームウェアのように機能を更新および拡張することもできます。







チップメーカーもそれほど遅れをとっておらず、FPGAを1つのパッケージにまとめてプロセッサを搭載し始めました。Intelは、FPGAの有名メーカーであるアルテラを買収して、FPGAを内蔵したPC用のプロセッサをリリースしました。







ユニバースのすべての真のプログラマーは、RTLを学習し、FPGAの「コード」を書き始める必要があるという信号を、通常のプロセッサーよりも悪くないと思います。

むかしむかし、私はこの方法で行って、スピードアップのためのいくつかのヒントを与えることができました。









まず、説明言語を選択する必要があります。 現時点では、System Verilog、SystemCなどのような言語を使用して、回路を作成するだけで、仕事よりも悪魔との取引のように見えます。 そのため、古い基本的なVHDLおよびVerilogはまだ使用中です。 私は両方を試し、後者を使用することを提案します。 Verilogはプログラマにとってより構文にやさしく、一般的にはより近代的です。







この方法を採用することに決めた場合は、キーワードと標準のVerilogデザインを既に知っていると思います。 時間をかけて、ハードウェアの説明ではすべてが同時に発生し、プログラムのように順番に発生しないことを理解しました。

ここでは、メタ安定性と信号競合の問題を残します。これは、同期リセットを使用した同期回路に限定し、すべての組み合わせと非同期性をオールドスクールに任せるためです。







回路の説明では、コード構造は非常に重要であり、その構成については後で説明します。 この構造は、コードの可読性と保守性を向上させるだけでなく、最終回路の結果にも影響を与えます。







実際のRTL設計者は、「図式的に」考え、コードをブロックに編成し、これによりその構造が決まります。 すぐに考え方を変えることはしませんが、「プログラマー」の記述を作成します。 取得したいものに集中し、これに適した回路の作成はシンセサイザーに任せます。 すべてが高水準言語の場合と同様に、コードを記述し、コンパイラーで最適化と機械コードへの変換を行います。







このアプローチの料金はほぼ同じで、リソースの観点からはやや劣りますが、前述のように、リソースの価格が下がるため、カートリッジを交換しません。 シンセサイザーはよりスマートになりましたが、まだいくつかの問題があります。例を考えてみましょう。







input clk; //  input data_we; //      input [7:0] data; //    reg [7:0] Data; // reg DataRdy; //   reg [7:0] ProcessedData; //  //     ---------------------- always @(posedge clk) begin if(data_we == 1'b1) //    begin Data <= data; //  DataRdy <= 1'b1; //    end end   №1
      
      





問題はありませんが、レセプションは別のブロックに割り当てられており、すべてが便利で理解しやすいものです。 次に、受信したデータをさらに処理し、新しいデータがいつ到着するかを理解するために、データ処理の最後にDataRdyフラグをクリアするとします。







 //    ---------------------- always @(posedge clk) begin if(DataRdy == 1'b1) //    begin //  ProcessedData <= Data; DataRdy <= 1'b0; // ,   end end2
      
      





ザイリンクス愛好家にとっては確かに問題が始まりますが、他のシンセサイザーは連帯すると思います。 シンセサイザーは、DataRdy信号には値を変更する2つのソースがあり、2ブロックで信号の前部に沿って変化し、クロック信号が1つしかないことは関係ないと言います。







DataRdyの値が1であるときに、両方のブロックで同時に変更条件が満たされた場合、シンセサイザーは設定する値を認識していないように見える場合があります







 //   if(data_we == 1'b1) DataRdy <= 1'b1; ... //   if(DataRdy == 1'b1) DataRdy <= 1'b0;   №3
      
      





ただし、この競合を解決するためにコードを変更しても役に立ちません。







 //     ---------------------- always @(posedge clk) begin //   ,     if((data_we == 1'b1)&&(DataRdy == 1'b0)) begin Data <= data; //  DataRdy <= 1'b1; //    end end4
      
      





論理的には、すべてが真実であり、競合はありませんが、シンセサイザーは信号の二重ソースについて常に文句を言い、彼とはうまくいきません。 すべてが正常に機能するために、異なるブロックの1つの信号を変更することは不可能であり、1つのブロックで受信して処理する必要があります。







最初の文はここにありますが、 モジュールには合計1つ常にブロックがあり、モジュールが実行するすべてがこのブロックに配置されます。この例は次のようになります。







 input clk; //  input data_we; //      input [7:0] data; //    reg [7:0] Data; // reg DataRdy; //   reg [7:0] ProcessedData; //  //--------------------------------------------------- //    //--------------------------------------------------- always @(posedge clk) begin //   ,     if((data_we == 1'b1)&&(DataRdy == 1'b0)) begin Data <= data; //  DataRdy <= 1'b1; //    end else if(DataRdy == 1'b1) //    begin //  ProcessedData <= Data; DataRdy <= 1'b0; // ,   end end   №5
      
      





これですべてが機能するようになりましたが、モジュールはあまり明確ではなく、受信と処理が明確に分離されており、すべてが1つのヒープになっています。 ここでは、Verilog言語の非常に優れた機能の1つを支援します。 1つのブロックで1つの変数に複数の割り当てを行った場合(非ブロック割り当てについて説明しています)、最後の割り当てが実行されます( Verilog HDL IEEE Std 1364-2001 Standard )。 これらはすべて説明された順序で実行されると言う方が正しいですが、そのような割り当てはすべて同時に発生するため、変数は最後に割り当てられた値を取ります。



つまり、次のように書くと:







 input B; reg [2:0] A; always @(posedge clk) begin A <= 1; A <= 2; A <= 3; if(B) A <= 4; end6
      
      





次に、 Bが偽の場合、 Aは値3を取りますが、 Bが真の場合、 Aは値4を取ります。これは次の画像で確認できます









図1 記述番号6の動作のシミュレーションのタイミング図







これは、標準で記述された完全に合成された構造であり、興味深い可能性を提供します.if- else if構造の複雑なチェーンを作成する必要はありません。 変数の条件と値を記述するだけで、他の条件やこの変数への割り当てを考えずに記述し、他のコードから分離されているかのように記述することができます。







次に、そのような割り当てを正しい順序で配置し、それによって同時実行の場合に優先順位を設定し、すべてが自動的に機能するようにします。 これはコードを管理する非常に便利な方法であり、人ではなくシンセサイザーによって制御されます。







次の例は、これがどのように見えるかを示しています。







 //--------------------------------------------------- //    //--------------------------------------------------- always @(posedge clk) begin // ,   ----- if(...) Data <= data; // ,   ---- if(...) Data <= Func(Data); //,   ------------- if(reset_n == 1'b0) Data <= 0; end   №7
      
      





モジュール作成曲線がどこに行っても、リセット状態が上記のすべてをオーバーライドすることを確認できます。ロジックで好きなだけエラーを作成でき、リセットが発生し、リセットブロックに記述されている値を変数に設定できます。







また、一度にデータの処理と受信の条件を突然満たした場合、データを処理し、新しい到着で上書きしないようにすることもできます。 これは、コード内の処理が低いために発生し、優先度が高くなります。 突然、ある時点で、着信データを失わないことがより重要であることに気付いた場合、ブロックを交換して優先順位を変更します。







データを変更できるインターフェイスが複数ある場合は、インターフェイスを実装するコードセクションを次々に配置するだけで、データアクセスの優先順位が決まります。







 //--------------------------------------------------- //    //--------------------------------------------------- always @(posedge clk) begin //   1 , //  ------------------- if(master1_we) Data <= data1; //   2 , //   ----------------- if(master2_we) Data <= data2; // ,   ---- if(need_process) Data <= (Data << 1); //,   ------------- if(reset_n == 1'b0) Data <= 0; end   №8
      
      





説明の動作のシミュレーションは、下の図で見ることができます









図2。 説明No. 8の動作のシミュレーションのタイミング図







このシステムはいくつかの主要なデバイスによって制御され、それらの間の調停は自動的に行われます。 ウィザードがスキームを順番に管理するとき(フェーズ1および2、図2)、各ウィザードからデータを受け取りますが、複数のマスターが突然同時にデータを提供する場合(フェーズ3、図2)、スキームは優先度の高いマスターからのデータを使用します、このインターフェイスの例を2番目の例から説明します。







この場合、回路リセットはすべての信号をカバーし(フェーズ5、図2)、処理はいずれのマスターよりも優先度が高くなりますが、リセット(フェーズ4、図2)よりは低くなります。







最初の例に戻り、説明の最終バージョンを示しましょう。







 input clk; //  input data_we; //      input [7:0] data; //    reg [7:0] Data; // reg DataRdy; //   reg [7:0] ProcessedData; //  //--------------------------------------------------- //    //--------------------------------------------------- always @(posedge clk) begin //  ------------------------------- // 0 if(data_we == 1'b1)//    begin Data <= data; //  DataRdy <= 1'b1; //    end //  -------------------------- // 1 if(DataRdy == 1'b1) //    begin //  ProcessedData <= Data; DataRdy <= 1'b0; // ,   end end   №9
      
      





受信ブロックでDataRdyの値がゼロであることを確認する必要さえありません。処理中に新しいデータが到着した場合でも、処理ブロックは受信ブロックを優先してオーバーライドし、DataRdyフラグをクリアします。 また、ブロックを交換することにより、新しいデータを見逃すことはありません。







 input clk; //  input data_we; //      input [7:0] data; //    reg [7:0] Data; // reg DataRdy; //   reg [7:0] ProcessedData; //  //--------------------------------------------------- //    //--------------------------------------------------- always @(posedge clk) begin //  -------------------------- // 0 if(DataRdy == 1'b1) //    begin //  ProcessedData <= Data; DataRdy <= 1'b0; // ,   end //  ------------------------------- // 1 if(data_we == 1'b1)//    begin Data <= data; //  DataRdy <= 1'b1; //    end end   №10
      
      





データの処理後、DataRdyフラグはリセットされますが、新しいデータが同時に到着した場合、受信ユニットはリセット優先度をブロックし、DataRdyフラグを再度設定します。データ(更新の事実)は失われず、データは次のサイクルで処理されます。







そのようなコード編成は何を提供しますか?







コードは明確なブロックに分割され、その前に長いコメントを付けることができます。これにより、各ブロックが作成されます。 ブロックに優先順位を付ける機能があり、あるブロックから別のブロックへの割り当てをオーバーライドしますが、 if - else if - else ifリストのように巨大な不快なものにバインドしません。 ブロックを削除または「コメントアウト」したり、ブロック間に別のブロックを挿入したりできます。残りのコードは変更なしで引き続き機能します。







常に単一であるため、ある時点で異なる構造ブロックの信号を変更することにした場合、デュアル信号ソースの競合はありません。 必要な場所とタイミングで信号を変更するだけです。 常に個人の場合のように、「ハンドシェイク」および「転送」の追加信号を整理する必要はありません。







コードは管理、読み取り、変更が可能で、理解可能な法律に従って存在します。優先順位エンコーダーを収集してインターフェースマルチプレクサーに送信し、バス内のすべての信号を収集し、信号を変更するためのすべての条件を把握する必要はありません。







必要なのは、単に回路の動作、必要なものを記述し、記述ブロックの位置によって優先順位を設定し、それをすべて処理のためにシンセサイザーに渡すだけです。 彼はタスクに完全に対応し、回路に目的の動作を与えると確信できます。ザイリンクスシンセサイザーは確かですが、他のものも連帯していると思います。








All Articles