ブロック RAM に格納された正弦波を使用してブリージング LED エフェクトを作成する方法
ここ数年で購入したガジェットの多くが、LED 点滅から LED 呼吸に移行していることに気付きました。ほとんどの電子ギズモにはステータス LED が含まれており、その動作によってデバイスの内部で何が起こっているかがわかります。
私の電動歯ブラシは、充電が必要になると LED を点滅させ、携帯電話はさまざまな理由で LED を使用して注意を喚起します。しかし、LED は昔のように点滅していません。これは、連続的に変化する強度を持つアナログ パルス効果のようなものです。
下の Gif アニメーションは、この効果を使用してバッテリーを充電していることを示す Logitech マウスを示しています。私はこの効果を呼吸LEDと呼んでいます 光の強さのパターンは、速度と加速度が人間の呼吸サイクルに似ているためです。照明サイクルが正弦波パターンに従うため、とても自然に見えます。
この記事は、パルス幅変調 (PWM) に関する先週のブログ投稿の続きです。今日は、前のチュートリアルで作成した PWM モジュール、のこぎりカウンター、およびリセット モジュールを使用します。下のリンクをクリックして、PWM に関する記事を読んでください!
VHDL で PWM コントローラを作成する方法
正弦波値をブロック RAM に格納
FPGA のデジタル信号処理 (DSP) プリミティブを使用して正弦波を生成することもできますが、より簡単な方法はサンプル ポイントをブロック RAM に格納することです。実行時に値を計算する代わりに、合成中に一連の正弦値を計算し、それらを格納する読み取り専用メモリ (ROM) を作成します。
以下の最小限の例を考えてみましょう。これは、完全な正弦波を 3×3 ビットの ROM に保存する方法を示しています。アドレス入力とデータ出力はどちらも 3 ビット幅です。つまり、0 から 7 の範囲の整数値を表すことができます。ROM には 8 つの正弦値を格納でき、データの分解能も 0 から 7 です。 .
三角正弦関数は、任意の角度入力 x に対して範囲 [-1, 1] の数値を生成します。 .また、x ≥ 2π の場合、このサイクルが繰り返されます。したがって、0 から 2π までの正弦値のみを格納するだけで十分です。 2π の正弦値は、ゼロの正弦値と同じです。上の図は、この概念を示しています。ゼロから \frac{7\pi}{4} までの正弦値を格納しています。これは、完全な 2π 円の前の最後の等間隔ステップです。
デジタル ロジックは、角度や正弦値などの実際の値を無限の精度で表すことはできません。これは、どのコンピュータ システムでも同じです。倍精度浮動小数点数を使用する場合でも、それは単なる概算です。これが 2 進数の仕組みであり、サイン ROM も同じです。
利用可能なデータ ビットを最大限に活用するために、ROM コンテンツを計算するときにオフセットとスケールを正弦値に追加します。以下の式に示すように、-1 の可能な最小の正弦値はデータ値 0 にマッピングされ、可能な最大の正弦値 1 は 2^{\mathit{data\_bits}-1} に変換されます。
\mathit{data} =\mathit{Round}\left(\frac{(1 + \sin \mathit{addr}) * (2^\mathit{data\_bits} - 1)}{2}\right)ROM アドレスを角度 x に変換するには、次の式を使用できます:
x =\frac{\mathit{addr} * 2\pi}{2^\mathit{addr\_bits}}もちろん、この方法では角度から正弦値への普遍的な変換はできません。それが必要な場合は、追加のロジックまたは DSP プリミティブを使用して、アドレス入力とデータ出力をスケーリングする必要があります。しかし、多くのアプリケーションでは、符号なし整数として表される正弦波で十分です。次のセクションで説明するように、これはまさに LED パルス プロジェクトの例に必要なものです。
サイン ROM モジュール
この記事で紹介する正弦 ROM モジュールは、ほとんどの FPGA アーキテクチャでブロック RAM を推論します。幅と深さのジェネリックをターゲット FPGA のメモリ プリミティブに一致させることを検討してください。これにより、リソースを最大限に活用できます。実際の FPGA プロジェクトで正弦 ROM を使用する方法がわからない場合は、Lattice プロジェクトの例をいつでも参照できます。
VHDL ファイルと ModelSim / iCEcube2 プロジェクトをダウンロードするには、以下のフォームにメールを送信してください!
実体
以下のコードは、sine ROM モジュールの実体を示しています。これには、推論されたブロック RAM の幅と深さを指定できる 2 つの汎用入力が含まれています。意図しない整数オーバーフローを防ぐために、定数に範囲指定子を使用しています。詳細については、この記事の後半で説明します。
entity sine_rom is generic ( addr_bits : integer range 1 to 30; data_bits : integer range 1 to 31 ); port ( clk : in std_logic; addr : in unsigned(addr_bits - 1 downto 0); data : out unsigned(data_bits - 1 downto 0) ); end sine_rom;
ポート宣言にはクロック入力がありますが、RAM プリミティブにはリセットがないため、リセットはありません。 アドレス input は、スケーリングされた角度を割り当てる場所です (x ) 値、および データ 出力は、スケーリングされた正弦値が表示される場所です。
型宣言
VHDL ファイルの宣言領域の先頭で、ROM ストレージ オブジェクトのタイプとサブタイプを宣言します。 addr_range subtype は RAM のスロット数に等しい整数の範囲で、rom_type すべての正弦値を格納する 2D 配列を記述します。
subtype addr_range is integer range 0 to 2**addr_bits - 1; type rom_type is array (addr_range) of unsigned(data_bits - 1 downto 0);
ただし、まだ storage シグナルを宣言するつもりはありません。まず、RAM を ROM に変換するために使用できる正弦値を生成する関数を定義する必要があります。関数を使用して初期値を ROM ストレージ シグナルに割り当てることができるように、シグナル宣言の上で宣言する必要があります。
addr_bits を使用していることに注意してください addr_range を定義するためのベースとしてジェネリック .これが、addr_bits の最大値を 30 に指定しなければならなかった理由です。 .より大きな値の場合、2**addr_bits - 1
計算がオーバーフローします。 VHDL 整数の長さは 32 ビットですが、これは 64 ビット整数を使用していた VHDL-2019 で変更されようとしています。しかし今のところ、ツールが VHDL-2019 のサポートを開始するまで、VHDL で整数を使用する場合、この制限に対処する必要があります。
正弦値を生成する関数
以下のコードは init_rom を示しています サイン値を生成する関数。 rom_type を返します そのため、最初に型を宣言し、次に関数を宣言し、最後に ROM 定数を宣言する必要があります。それらは正確な順序で相互に依存しています。
function init_rom return rom_type is variable rom_v : rom_type; variable angle : real; variable sin_scaled : real; begin for i in addr_range loop angle := real(i) * ((2.0 * MATH_PI) / 2.0**addr_bits); sin_scaled := (1.0 + sin(angle)) * (2.0**data_bits - 1.0) / 2.0; rom_v(i) := to_unsigned(integer(round(sin_scaled)), data_bits); end loop; return rom_v; end init_rom;
この関数は、rom_v を含むいくつかの便利な変数を使用します 、正弦値で埋める配列のローカル コピー。サブプログラム内では、for ループを使用してアドレス範囲を反復処理し、ROM スロットごとに、前に説明した式を使用して正弦値を計算します。そして最後に、rom_v を返します 変数にはすべてのサイン サンプルが含まれています。
for ループの最後の行の整数変換が、data_bits を制限しなければならなかった理由です。 31 ビットにジェネリック。それ以上のビット長ではオーバーフローします。
constant rom : rom_type := init_rom;
init_rom の下 関数定義、rom の宣言に進みます オブジェクトを定数として。 ROM は単に書き込みを行わない RAM であるため、まったく問題ありません。それでは、関数を使用します。 init_rom を呼び出します 上記のコードに示すように、初期値を生成します。
ROM プロセス
正弦 ROM ファイルの唯一のロジックは、以下にリストされているかなり単純なプロセスです。 1 つの読み出しポートを備えたブロック RAM について説明します。
ROM_PROC : process(clk) begin if rising_edge(clk) then data <= rom(to_integer(addr)); end if; end process;
トップ モジュール
この設計は、以前のブログ投稿で紹介した PWM プロジェクトの続きです。リセット モジュール、PWM ジェネレーター モジュール、フリーランニング クロック サイクル カウンター (鋸歯カウンター) モジュールを備えています。これらのモジュールがどのように機能するかについては、先週の記事を参照してください。
下の図は、最上位モジュールのサブモジュール間の接続を示しています。
次のコードは、最上位 VHDL ファイルの宣言領域を示しています。先週の PWM 設計では、duty_cycle オブジェクトは cnt の MSB のエイリアスでした カウンター。しかし、正弦 ROM からの出力がデューティ サイクルを制御するため、現在は機能しないため、実際の信号に置き換えました。さらに、addr という名前の新しいエイリアスを作成しました これはカウンターの MSB です。 ROMのアドレス入力として使用します。
signal rst : std_logic; signal cnt : unsigned(cnt_bits - 1 downto 0); signal pwm_out : std_logic; signal duty_cycle : unsigned(pwm_bits - 1 downto 0); -- Use MSBs of counter for sine ROM address input alias addr is cnt(cnt'high downto cnt'length - pwm_bits);
以下のリストの一番上のモジュールで新しい正弦 ROM をインスタンス化する方法を確認できます。 RAM の幅と深さを、PWM モジュールの内部カウンターの長さに合わせて設定します。 データ ROM からの出力が duty_cycle を制御します PWM モジュールへの入力。 duty_cycle の値 信号は、RAM スロットを次々に読み取るときに正弦波パターンを表します。
SINE_ROM : entity work.sine_rom(rtl) generic map ( data_bits => pwm_bits, addr_bits => pwm_bits ) port map ( clk => clk, addr => addr, data => duty_cycle );
正弦波 ROM のシミュレーション
下の画像は、ModelSim での最上位モジュールのシミュレーションの波形を示しています。署名されていない duty_cycle の表示を変更しました 信号をアナログ形式に変換して、正弦波を観測できるようにします。
led_5 です 外部 LED を制御する PWM 信号を伝送する最上位出力。デューティ サイクルが上昇または下降しているときに、出力が急速に変化していることがわかります。しかし、デューティ サイクルが正弦波の頂点にある場合、led_5 安定した「1」です。波がスロープの底にあるとき、出力は一時的に「0」のままになります。
自宅のコンピューターで試してみませんか?以下のフォームにメール アドレスを入力して、VHDL ファイルと ModelSim および iCEcube2 プロジェクトを受け取りましょう!
FPGA での LED ブリージングの実装
Lattice iCEcube2 ソフトウェアを使用して、iCEstick FPGA ボードにデザインを実装しました。上記のフォームを使用してプロジェクトをダウンロードし、iCEstick をお持ちの場合はボードで試してみてください!
以下のリストは、iCEcube2 に付属の Synplify Pro ソフトウェアによって報告されたリソースの使用状況を示しています。これは、デザインが 1 つのブロック RAM プリミティブを使用していることを示しています。これが正弦 ROM です。
Resource Usage Report for led_breathing Mapping to part: ice40hx1ktq144 Cell usage: GND 4 uses SB_CARRY 31 uses SB_DFF 5 uses SB_DFFSR 39 uses SB_GB 1 use SB_RAM256x16 1 use VCC 4 uses SB_LUT4 65 uses I/O ports: 7 I/O primitives: 7 SB_GB_IO 1 use SB_IO 6 uses I/O Register bits: 0 Register bits not including I/Os: 44 (3%) RAM/ROM usage summary Block Rams : 1 of 16 (6%) Total load per clock: led_breathing|clk: 1 @S |Mapping Summary: Total LUTs: 65 (5%)
iCEcube2 でデザインをルーティングすると、.bin が見つかります。 led_breathing_Implmnt\sbt\outputs\bitmap のファイル Lattice_iCEcube2_proj 内のフォルダ プロジェクト ディレクトリ
iCEstick ユーザー マニュアルに示されているように、Lattice Diamond Programmer Standalone ソフトウェアを使用して FPGA をプログラムできます。それが私が行ったことであり、下の Gif アニメーションはその結果を示しています。 LED の光強度は正弦波パターンで振動します。非常に自然に見え、想像力を働かせると LED が「呼吸」しているように見えます。
最終的な考え
ブロック RAM を使用して計算済みの正弦値を格納するのは、非常に簡単です。ただし、この方法は X と Y の解像度が制限された正弦波にのみ適しているという、いくつかの制限があります。
頭に浮かぶ最初の理由は、先ほど説明した整数値の 32 ビット制限です。サイン値をより巧妙に計算することで、この問題を克服できると確信していますが、それだけではありません。
ROMアドレスを拡張するビットごとに、RAMの使用量が2倍になります。 X 軸で高い精度が必要な場合は、大規模な FPGA でも十分な RAM がない可能性があります。
Y 軸のサイン値に使用されるビット数がネイティブ RAM の深さを超える場合、合成ツールは追加の RAM または LUT を使用して ROM を実装します。 Y の精度を上げると、リソースの予算がさらに消費されます。
理論的には、正弦波の 1 つの象限を保存するだけで済みます。したがって、有限状態マシン (FSM) を使用して ROM の読み出しを制御すると、RAM の使用量を 4 分の 1 に抑えることができます。 X 軸と Y 軸の 4 つの順列すべてについて、正弦象限を反転する必要があります。次に、ブロック RAM に格納された 1 つの象限から完全な正弦波を作成できます。
残念ながら、4 つのセグメントすべてをスムーズに結合することは困難です。正弦波の上部と下部のジョイントにある 2 つの等しいサンプルは、正弦波にフラット スポットを作成することによってデータを歪めます。ノイズを導入すると、正弦波の精度を向上させるために象限のみを保存するという目的が無効になります。
VHDL
- AWSを使用してCloudFormationテンプレートを作成する方法
- 摩擦のないUXを作成する方法
- VHDL で文字列のリストを作成する方法
- VHDL コード ロック モジュール用の Tcl 駆動型テストベンチを作成する方法
- VHDL で PWM コントローラーを作成する方法
- TEXTIO を使用してファイルから RAM を初期化する方法
- セルフチェック テストベンチの作成方法
- VHDL でリンク リストを作成する方法
- Java でオブジェクトの配列を作成する方法
- 医療専門家がデジタルマニュファクチャリングを使用して次世代の解剖学的モデルを作成する方法
- 情報モデルを使用して OPC UA クライアントからファンクション ブロックを呼び出す方法