- ZYBOでシンセサイザー作成(1)LチカとHelloWorld
- ZYBOでシンセサイザー作成(2)オーディオコーデックの使用
- ZYBOでシンセサイザー作成(3)波形テーブル(BRAM)による正弦波の合成
- ZYBOでシンセサイザー作成(4)パラシリ変換 IP の作成
- ZYBOでシンセサイザー作成(5)音を出力する
今回は、波形テーブル(BRAM)を使って正弦波を合成するIP (Intellectual Property) を製作します。前回まではハードウェア記述言語(HDL)を用いずにFPGAを使っていましたが、今回からHDLのSystemVerilog を用いていきたいと思います。
今回の記事で参考にした文献は以下の2つです。
[1] 波形テーブルの参考文献
[2] IP作成の参考文献環境
- パソコン:Windows10 64 bit
- Vivado 2020.2 をインストール
- Xilinx Vitis 2020.2 をインストール
- ボード:Zybo Z7-20
波形テーブルによる合成について
SystemVerilogでは三角関数でさえ計算するのに苦労します。そこで、波形テーブル(BRAM)を使用して正弦波を合成します。一応、Xilinx社が三角関数を計算する IP を提供していますが、波形テーブルによる合成は様々な状況で応用が効くと思いますので、こちらの方法で合成します。
波形テーブルによる合成の例
波形テーブルによる合成では、図1のような1周期の波形をRAMの中に保存しておきます。図1の場合、2048個のデータを保存する必要があります。この波形テーブルから順次データを取り出すことによって任意周波数の波形を合成することができます。
例えば、サンプリングレート が48kHzの場合、周波数 が12kHzの波形を合成したいなら、0番目、512番目、1024番目、1536番目、0番目...と取り出せばよいです。
任意周波数の正弦波の合成
周波数 の信号を合成したい場合、データを取り出す波形テーブルの番号 は以下の式で計算されます [1]。
ここで、はサンプリング周波数、 は波形テーブルのデータ数、はサンプル番号です。 というのは「をで割ったときの余り」です。この式をSystemVerilogで計算する必要があります。
正弦波合成 IP の製作
VivadoでBRAMを使った正弦波合成 IP を製作していきます。
正弦波合成 IPのフォルダの用意
まず、以下のようにフォルダを作成して、ファイルを配置していきます。 sin_gen.sv と SinTbl.coe はこちらの GitHub の「3rd」フォルダの中にあります。
ip_repo_synth └── sin_gen ├── HDL │ └── sin_gen.sv └── ROMDATA └── SinTbl.coe
sin_gen.svの内容を以下に示します。sin_gen.svの内容については別の記事でしたいと思います。
module sin_gen( input logic clk96M, input logic reset, input logic [14:0] freq, output logic [15:0] dout ); logic [20:0] cnt; // Int 21bit logic [10:0] cnt2000; logic cnt2000_flag; logic [14:0] freq_ff; // Int 15bit logic chg_freq_flag; logic [35:0] p_int; // Int 36bit logic [39:0] p_int_ff; // Int 40bit logic [50:0] p; // Int 32bit Decimal 19bit logic [10:0] p_ff; // Int 11bit localparam tmp = 11'd1398; // Int 1bit, Decimal 10bit // cnt2000 always_ff @(posedge clk96M) begin if(reset) cnt2000 <= 11'd0; else if(cnt2000_flag) cnt2000 <= 11'd0; else cnt2000 <= cnt2000 + 11'd1; end // cnt2000_flag assign cnt2000_flag = (cnt2000 == 11'd1999); // freq_ff always_ff @(posedge clk96M) begin if(reset) freq_ff <= 15'd0; else freq_ff <= freq; end // chg_freq_flag assign chg_freq_flag = (freq != freq_ff); // cnt always_ff @(posedge clk96M) begin if(reset) cnt <= 21'd0; else if(chg_freq_flag) cnt <= 21'd0; else if(cnt2000_flag) cnt <= cnt + 21'd1; else cnt <= cnt; end // p_int assign p_int = cnt*freq; // p_int_ff always_ff @(posedge clk96M) begin if(reset) p_int_ff <= 40'd0; else p_int_ff <= {p_int, 4'd0}; end // p assign p = p_int_ff * tmp; // p_ff always_ff @(posedge clk96M) begin if(reset) p_ff <= 11'd0; else p_ff <= p[29:19]; end // BRAM WAVE_TBL WAVE_TBL( .addra (p_ff), .douta (dout), .clka (clk96M) ); endmodule
SinTbl.coeの内容を以下に示します。振幅16384、波形テーブルのデータ数 の正弦波の波形テーブルとなっています。Pythonを用いて符号付き整数で出力しました。
memory_initialization_radix=16; memory_initialization_vector= 0000, 0032, 0065, 0097, 00c9, … ffce;
VivadoでIPの製作
プロジェクトを以下のようにして作成します。FPGAに書き込むためのプロジェクトではないので、制約ファイルは作らなくていいです。
- 作業フォルダ:sin_wave_gen
- プロジェクト名:sin_wave_gen
このプロジェクトでIPの製作を行い、IPのシミュレーションも行います。
プロジェクトができたら、「Tools」→「Create and Package New IP...」を実行します。
最初の画面(図3)では、そのまま「Next」をクリックします。
「Packaging Options」(図4)では、「Package a specified directrory」を選択して、「Next」をクリックします。これでフォルダを指定することでIPが作成されます。
IPのフォルダはさきほど作成した「sin_gen」を選択して、「Next」をクリックします(図5)。自分は「ip_repo_synth」というシンセサイザー用のIPリポジトリに「sin_gen」を置いてありますので、それを選択しました。
次の画面(図6)では、IPを編集するプロジェクト名の入力をします。ここはデフォルトのまま「Next」をクリックします。
最後の画面(図7)では、そのまま「Finish」をクリックします。HDLのSystemVerilogファイルにエラーがある場合、エラーが生じて、IPが作成できないので気をつけてください。
IP編集用のVivado画面が図8のように起動され、「sin_gen/HDL」フォルダ内の回路記述が自動で読み込まれます。起動した画面の「Sources」ペインでは「WAVE_TBL」に?マークがあり、プロジェクトにBRAMが不足しています。そのため、BRAMを追加していきます。
BRAMを追加するには「PROJECT MANAGER」→「IP Catalog」を実行します。IPカタログ内の「Memories & Storage Elements」→「RAMs & ROMs & BRAM」→「Block Memory Generator」をダブルクリックします。
BRAM生成用のウィザードではタブごとに設定を変更していきます。まず、「Basic」タブ(図10)では以下のように設定を変更します。
- Component Name: WAVE_TBL
- Memory Type: Single Port ROM
「Single Port ROM」とすることで、1ポートのROMとなります。RAMでもよいのですが、信号線が多くなるのでROMにしています。
「Port A Options」タブでは、以下のように設定を変更します。
- Port A Depth: 2048
- Enable Port Type: Always Enabled
- Primitives Output Register: チェックOFF
「Port A Depth」で波形テーブルのデータ数を設定しています。「Enable Port Type」では「Always Enabled」にすることでイネーブル信号を不要としています。また「Primitives Output Register」をチェックOFFすることで出力にレジスタ(FF)を付けないようにしています。
「Other Options」タブでは、以下のように設定を変更します。
- Load Init File: チェックON
- Coe File: sin_gen/ROMDATA/SinTbl.coe を選択
これで、SinTbl.coeに記述してあるデータがBRAMにロードされます。
最後に、「Summary」タブで設定を確認します。以下のようになっていることを確認してください。
- Memory Type: Single Port ROM
- Block RAM resource(s)(18K BRAMs): 0
- Block RAM resource(s)(36K BRAMs): 1
- Total Port A Read Latency: 1 Cock Cycle(s)
- Address Width A: 11
波形テーブルのデータ数を2048にすることで、36K BRAMsをちょうど1個使うようにしています。
保存場所を確認して(図14)、「OK」をクリックします。
また、「Create Output Products」の画面では、「Out of context per IP」を選択して、「Generate」をクリックします。「Out of context per IP」の場合、個々のIPごとに論理合成を行うので、全体の論理合成時間が短いというメリットがあります。一方、「Global」の場合、全体の論理合成時にIPを再合成するので、論理合成時間は長いが、生成ファイルが少ないという特徴があります。
これで、「Package IP」→「Files Groups」の「Merge changes from File Groups Wizard」をクリックして(図16)、「Synthesis(3)」となれば、問題ないです。
ただ、基本的にはVivadoの不具合のため「Synthesis(2)」となり、WAVE_TBLが読み込まれません(図17)。
対策方法としては、一度削除し追加することです。
- WAVE_TBLを「Remove File from Project...」する
- 「PROJECT MANAGER」→「Add Sources」からWAVE_TBLを追加
- 「Merge changes from File Groups Wizard」をクリックすると、「synthesis(3)」となる(図19)
「Add Sources」するときは「Add or create design sources」を選択して、「sin_gen/src/WAVE_TBL/WAVE_TBL.xci」を追加してください。また、Copy sources into IP DirectoryはチェックOFFしてください(図18)。
最後に「Package IP」→「Review and Package」の「Package IP」をクリックすることでIPが作成されます(図20)。
プロジェクトを閉じていいかのダイアログが出ますので(図21)、「OK」をクリックしてください。
以上でIPが作成され、元のVivadoプロジェクト画面に戻ります。
IP のシミュレーション
「sin_wave_gen」プロジェクトで IP「sin_gen」のシミュレーションをしていきます。
IP インテグレータで回路作成
IPインテグレータで回路を作っていきます。
- 「IP INTEGRATOR」→「Create Block Design」で「Diagram」を作成
- 「Add IP」で「sin_gen_v1_0」を追加
- 「sin_gen_v1_0」の全てのポートに対して「Make External」を実行
- ポート名を「_0」がないように変更
最終的な回路は図22のようになります。
回路ができたら、「Validate Design」をして、回路のチェックを行います。また、「Create HDL Wrapper」でラッパーを作成します。いつも通り「Copy generated wrapper to allow user edits」にしてください。
テストベンチの追加
回路のシミュレーションを行うためのファイルであるテストベンチをプロジェクトに追加します。追加するテストベンチ「sim_sin_gen.sv」はGitHubにあります。
このテストベンチをプロジェクトディレクトリの中に置いて、読み込みます。読み込む手順は以下です。
- 「PROJECT MANAGER」→「Add Sources」を実行
- 「Add or create simulation sources」を選択
- 「Add Files」で「sim_sin_gen.sv」を選択
- 「Scan and add RTL include files into project」と「Include all design sources for simulation」をチェックON(図23)
- 「Finish」をクリックする
シミュレーションの実行
シミュレーションを実行をします。シミュレーションは「SIMULATION」→「Run Simulation」→「Run Behavioral Simulation」で実行できます(図24)。シミュレーションファイルにエラーがある場合は、実行できないので気をつけてください。
シミュレーションの実行が上手くいくと図25のようになります。1nsまではシミュレーションが実行されています。「Run All」ボタンをクリックすることで、全てのシミュレーションが実行されます。
今回の場合は80msまで実行されます。「Zoom Fit」をクリックすることで、図26のように全体の結果をみることができます。
このままでは、doutが正弦波になっているかわかりづらいので、表示の仕方を以下のように変更します。
- 「dout」を右クリックして、「Waveform Style」→「Analog」を選択
- 「Waveform Style」→「Analog Setting」で「Y Range」を「Fixed」にして、Minを-18000、Maxを18000に変更(図27)
- 「dout」を右クリックして、「Radix」→「Signed Decimal」を選択
表示の仕方を変更すると図28のようになります。正弦波になっていることがわかると思います。
おわりに
今回で正弦波を作成することができました。次回はパラシリ変換器を作成してスピーカーから音を出したいと思います。また、SystemVerilogの説明を全くしていないので、補足記事を書きたいと思います。