このドキュメントは http://icrus.org/fpga_atlys_design/ 上にあります.
本日は音IC(AIC)制御について述べます. 皆さんも,パソコンのオーディを機能を使って,音楽をダウンロードしたり,音楽を聴いたりしていると思います.音楽を聴くときに働いているのがAICです.主な仕事はパソコンのディジタル信号をアナログ信号に変換し増幅し,ヘッドホンを通して,人間の聞こえる音を出すことです.また,録音のためには,マイクから入力したアナログ信号をパソコンが処理できるディジタル信号に変えたりもします.
もちろんインターネット上で扱える音データもこのディジタル信号です.最近はこれを利用して,適当に圧縮して通話(Skype,LINE)を行ったりもしています.このAIC,Atlys上にも搭載されていて,FPGAから自由に制御できます.ディジタル時計の延長線上で音の制御もできるのです.
FPGAプログラミングはなかなか大変.でもコツさえつかめばFPGAや周辺デバイスを自由に扱えるようになる.FPGAを扱うためのプログラミング(VerilogHDLやVHDLによる)も,C,Javaその他いずれか一つ1つの言語をある程度書けるようになっている人であれば,容易に文法を類推して作業が進められるだろう.さらに作業を進める上で必要なのは,ツールの使い方に慣れることと,デバイスのデータシートを読めるようになることだ.でも,これまで一切プログラミング言語を使ったことがない人は,まずは何か一つプログラミング言語を学ぼう.(どちらかというとVerilogはC言語に比較的近い.)今までプログラミングによりコンピュータの機能を自由に扱えるようになった経験がある人だったら,それと同様のプロセスをたどってFPGAを扱えるようになるだろう.
では,ここまで体験したツールのインストール,利用方法,簡単なVerilogプログラミングを踏まえて,今回は,ATLYSの音声入出力に的を絞って体験していこう.
一足飛びに結論から言えば,ATLYSの音声出力端子(黒色のヘッドホン端子)から音声(音楽や人の声など)を出力するためには, 第一章でLEDを光らせたように,オーディオIC(以下AICと略称する,実際にはLM4550Bという電子部品)を駆動・制御する必要がある.駆動とは具体的に言うとAICのデータフォーマットに従ってデータを作り,それをAICに送ることだ.まず,機能データで初期化して,次にディジタル音声データを繰り返し送りつけることができるようにできれば,AICから音がヘッドホンに出力されることになる.なお,ディジタルデータを音にして出力するというこの機能が「音声信号のDA変換(ディジタル・アナログ変換)」である.
何はともあれ,音を出してみよう.電源を入れると自動起動されるATLYS上のテストプログラムには実はすでに音声出力が実装されているのだ.ただ,それを動かすには1つ条件がある.購入後,本体のフラッシュROMにまだ自分のプログラムを一度も書き込んでいないことだ.では,音を聞いてみよう.
次の手順で本体を起動する.①ヘッドホンを黒色のオーディオ端子にさす.②電源スイッチをONにする.③プッシュ・スイッチN4,P4,F5,F6,P3,T15を押して,ヘッドホンからの音声出力を確かめる.音は高い単音または低い単音が出力され,AICが動作中であることが確認できる.(このとき,フラッシュROMに書き込みを行っていないのに,音が出力されない場合は,ヘッドホンやAICの故障が考えられる.)これが確認できればこっちのものである.あとは, FPGAとAIC間の配線名称を確認して,AICのデータシートを理解し,VerilogHDLまたはVHDLでプログラミングすればよい.(といってもここからが遠い道のりなので,がんばってついてきてほしい.)
千里の道も一歩から.まずは,verilogで記述したプログラムを打ち込んで合成してみよう.(合成Synthesizeとはプログラミング言語のコンパイルにあたる.)合成手順は,前回までにやった通りだからここでは説明しない.まず,新しいプロジェクトを作って,空のソース(*.vと*.ucfの2ファイル)を作成して以下<リスト1,2>に提供するプログラムをテキスト入力する.(なお,*.vファイルの各行の「//」以降,*.ucfファイルの「#」以降はコメント文なのでプログラムの動作とは関係なく,入力の必要はないが,入力しておくことでプログラムが読みやすくなる.)合成が官僚したら,Adept(iMPACTでも良い)を使ってATLYS側に*.bitファイルを転送する.
転送完了と同時にATLYS上で*.binファイルが起動して音が再生される.どのような音が出るだろうか?
00 `timescale 1ns / 1ps
01 /////////////////////////////////////////////////////////
02 // Module Name: audio_out
03 /////////////////////////////////////////////////////////
04
05 module audio_out( aud_bit_clk, aud_sdo, aud_sync, aud_reset,
06 sw, led ); // AIC out *****
07 input aud_bit_clk;
08 output reg aud_sdo, aud_sync;
09 output aud_reset;
10
11 input[1:0] sw;
12 output[7:0] led;
13
14 assign aud_reset = sw[0];
15 assign led = c[23:16];
16
17 reg[ 7:0] count=8'd255;
18 reg n_frame=1'b1;
19
20 reg[23:0] c=24'h000000;
21 reg signed[19:0] wave=20'h00000;
22
23 parameter tag = 16'b_1111_1000_0000_0000;
24 parameter[19:0] control[0:3] = { // add0, add1, data0, data1
25 20'h_04_000, 20'h_18_000, 20'h_0000_0, 20'h_0808_0
26 //HP_VOL_ADD PCM_VOL_ADD HP_VOL_DAT PCM_VOL_DAT
27 };
28
29 // Sine-wave generator (Preparation)
30 reg signed[31:0] y0;
31 reg signed[15:0] y1 = 16'h_03af, y2 = 16'h_0000;
32 parameter signed[15:0] a1 = 16'h_7fc9, a2 = 16'h_c000;
33
34 always @( posedge aud_bit_clk ) begin
35 if( count==8'd255 || count<=8'd14 )
36 aud_sync=1'b1;
37 else
38 aud_sync=1'b0;
39
40 if( count<=8'd15 )
41 aud_sdo=tag[15-count];
42 else if( count<=8'd35 )
43 aud_sdo=control[n_frame][35-count];
44 else if( count<=8'd55 )
45 aud_sdo=control[n_frame+2][55-count];
46 else if( count<=8'd75 )
47 aud_sdo = wave[75-count];
48 else if( count<=8'd95 )
49 aud_sdo = wave[95-count];
50 else
51 aud_sdo = 1'b0;
52
53 if( count==8'd255 )begin
54 // Sine-wave generator
55 y0 = y1*a1 + y2*a2;
56 y2 = y1;
57 y1 = { y0[31], y0[28:14] };
58 if(sw[1]==1'b1)
59 wave = y0[31:12];
60 else
61 wave = y0[31]==1'b1 ? 20'h_f_0000 : 20'h_0_ffff;
62
63 n_frame = n_frame+1'b1;
64 end
65
66 count = count + 1'b1;
67 c = c + 1'b1;
68 end
69 endmodule
00 NET "aud_bit_clk" LOC = "L13";
01 NET "aud_sdo" LOC = "N16";
02 NET "aud_sync" LOC = "U17";
03 NET "aud_reset" LOC = "T17";
04
05 NET "led(7)" LOC = "N12";
06 NET "led(6)" LOC = "P16";
07 NET "led(5)" LOC = "D4";
08 NET "led(4)" LOC = "M13";
09 NET "led(3)" LOC = "L14";
10 NET "led(2)" LOC = "N14";
11 NET "led(1)" LOC = "M14";
12 NET "led(0)" LOC = "U18";
13 NET "sw(0)" LOC = "A10";
14 NET "sw(1)" LOC = "D14";
いくつか,入力→合成→ファイル転送を行っている時に起きる代表的なトラブルを紹介しよう.もしかすると,これに該当する読者もいるかもしれない.
「ピー」という音が再生されただろうか?これは440Hzの正弦波で物理や電気で良く登場する波で1秒間に440回振動する.正弦波は音,音楽,物理,電気の基本である.音楽ではピアノ鍵盤の中央から少し左の「ラ」の音がこれにあたる.NHK等の時報「ピッ・ピッ・ピッ・ポーン」の3こある「ピッ」がこれだ.(地上デジタル放送では時間遅れの関係で放送されなくなった.)
プログラムを手入力したみなさんなら解読しながら打ち込んだと思うので,当然,スライドスイッチA10(sw[0]),D14(sw[1])の動作については,予想がついたと思うがsw[0]はAICのリセット端子につながっていて,0(OFF)のときAICは動かない.またsw[1]は1(ON)で正弦波,0(OFF)で方形波を出力する.(実際はDA変換後のローパスフィルタの関係で正確な方形波にはならない.)
パソコンの音声入力を利用したFFTスペクトルアナライザ(http://efu.jp.net/soft/ws/ws.htmlページの下の方からWS151.ZIPをダウンロード・インストール)を利用して,音を見てみよう.手順は以下の通り.①ATLYSの音声出力をパソコンの音声入力につなぐ.②WS.EXEのアイコンをダブルクリックして実行する.③録音ボタン(赤丸のボタン)をクリックする.④入力波形が表示され,周波数スペクトルが表示される.
周波数を変更してみよう.提供したプログラムは440Hzの正弦波と方形波をsw[1](A10)のON/OFFで切り替えて出力するように書かれている.周波数は,31,32行目のy1,a1の値で決まるので,以下のように変更してみよう.どのような音が出力されるだろうか?周波数スペクトル表示して測定しよう.
変更前 変更後
y1 = 16'h_03af y1 = 16'h_1090;
a1 = 16'h_7fcf a1 = 16'h_7ba3;
発振したい周波数を\(f\)[Hz],サンプリング周波数を\(f_s\)[Hz],円周率\(\pi\)として
sin,cos関数から得られた実数のままでは使えないので16,384(=2^14)を掛けて,16ビット符号付き整数(2進数の左端の桁から2桁目と3桁目の間に少数点がある符号付き固定小数点表示実数00.00000000000000)にする.fに希望する周波数を代入し,式の結果を用いてy1,a1を変更すれば必要な周波数の正弦波を得られる.
33行目に追加
reg[31:0] r = 32'h0;
61行目に入れ替え
begin
r=r*214013+2531011;
wave = r[19:0];
end;
以上を変更して,合成して,ATLYSで実行すると疑似ホワイトノイズを発生できる.ホワイトノイズは,周波数特性が一定でフィルタの周波数特性を観測するときにも用いられる(後述).FM放送帯で放送がない周波数で聴取できる「ザー」というノイズ音声である.
スライドスイッチを切り替えることにより,ヘッドホンのLチャンネルのみ,または,Rチャンネルのみから音声が出力されるようにする.
プログラムの46,47行目がLチャンネル出力,48,49行目がRチャンネル出力である.
① moduleの引数として,aud_sdi を追加.(入力ビットストリーム受信用)
② 以下の変数宣言をmodule冒頭部分に追加.(入力データを扱うための変数)
input aud_sdi;
reg signed[19:0] wave_in0, wave_in1;
③ 18行目のn_frame宣言を変更
reg[1:0] n_frame=2'b11;
④ 24~27行目のparameter定義を変更.(音声入力AD変換用設定データを追加)
parameter[19:0] control[0:7] = {
// add0, add1, add2, add3, data0, data1, data2, data3
20'h_04_000, 20'h_18_000, 20'h_1A_000, 20'h_1C_000,
20'h_0000_0, 20'h_0808_0, 20'h_0404_0, 20'h_0000_0
};
⑤ 45行目変更.(設定データをAICに渡す.)
aud_sdo=control[n_frame+4][55-count];
⑥ 52行目に追加.(L/Rチャンネル音声入力データをレジスタに受け取る)
//--- sdata_in ---
if( count>=8'd57 && count<=8'd76 ) // Adding
wave_in0[76-count] = aud_sdi;
else if( count<=8'd96 )
wave_in1[96-count] = aud_sdi;
wave = wave_in0;
ATLYSのリファレンスマニュアルP14-15を見るとATLYS上のAICの解説がある.FPGAとAIC間の「つなぎ」(接続)について必要部分を抜粋すると次のようになる.
信号名 | FPGA PIN | 信号方向 FPGA←→AIC | AIC PIN | 備考 | |
① | AUD_BIT_CLK | L13 | ← | 6 | クロック信号 |
② | AUD_SDI | T18 | ← | 8 | 音声入力データ |
③ | AUD_SDO | N16 | → | 5 | 音声出力データ |
④ | AUD_SYNC | U17 | → | 10 | 同期信号(アクティブHi) |
⑤ | AUD_RESET | T17 | → | 11 | リセット信号(アクティブLow) |
[ポイントA] ①のクロック信号は,AICからFPGAに送られ,AICの操作のための信号はこのクロックに同期させる必要がある.
[ポイントB] 音声を出力する場合は③を通してディジタルデータをFPGAからAICに送る.
[ポイントC] 外部からマイクなどで音声を入力する場合は②を通してディジタルデータをAICからFPGAに送る
[ポイントD] リセット信号でAICを初期化し,同期信号でデータの先頭をAICに知らせる.
FPGAからAICに送られるデータ(AUD_SDO)は図5-3のようになる.図中1行目のSYNC(Hi)のタイミングで始まり,図中2行目のTAG,CMD ADR,CMD DATA,PCM LEFT, PCM RIGHT,…の順に第0~12の13スロットが送られる.CMD ADR,CMD DATAはAICの設定調整のデータであり,設定レジスタマップに従う.PCM LEFT, PCM RIGHTはAICに送られるデータで,PCM LEFTは左の音20ビット,PCM RIGHTは右の音20ビットである.(第5~12スロットは今回は使用しない.)
図中3行目がAICからFPGAに送られるデータ(AUD_SDI)で第3スロットがPCM LEFT,第4スレットがPCM RIGHTである.
スロット0のTAG のみ16ビット,他のスロットは20ビットの長さがあり,1フレームのビット数は16+20×12=256ビットである.
図5-4からわかるように,フレーム内のビットのHi/LowはBIT_CLKの立ち下がりでAIC側が判断する.従ってデータビットは,BIT_CLKの立ち上がり後に変更し,立ち下がり時には安定していなければならない.
000`timescale 1ns / 1ps
020
021 module audio_inout( aud_bit_clk, aud_sdo, aud_sync, aud_reset,
022 aud_sdi, // Adding
023 sw, led ); // AIC teset ********************************
024 input aud_bit_clk;
025 output reg aud_sdo, aud_sync;
026 output aud_reset;
027
028 input aud_sdi; // Adding
029 reg signed[19:0] wave_in0, wave_in1; // Adding
030
031 input[1:0] sw;
032 output[7:0] led;
033
034 assign aud_reset = 1'b1;//sw[0];
035 assign led = c[23:16];
036
037 reg[7:0] count=8'd255;
038 reg[1:0] n_frame=2'b11; // Adding(change)
039
040 reg[23:0] c=24'h000000;
041 reg signed[19:0] wave=20'h00000;
042
043 parameter tag = 16'b_1111_1000_0000_0000;
044
045 parameter[19:0] control[0:7] = { // Adding(change)
046 // add0, add1, add2, add3, data0, data1, data2, data3 // Adding(change)
047 20'h_04_000, 20'h_18_000, 20'h_1A_000, 20'h_1C_000, // Adding(change)
048 20'h_0000_0, 20'h_0808_0, 20'h_0404_0, 20'h_0000_0 // Adding(change)
049 }; // Adding(change)
050
051 // Sine-wave generator (Preparation)
052 reg signed[31:0] y0;
053 reg signed[15:0] y1 = 16'h_03af, y2 = 16'h_0000;
054 parameter signed[15:0] a1 = 16'h_7fc9, a2 = 16'h_c000;
055
056 wire clk = (sw[0]==1'b1 ? aud_bit_clk : 1'b0);
057 always @( posedge clk ) begin
058 if( count==8'd255 || count<=8'd14 )
059 aud_sync=1'b1;
060 else
061 aud_sync=1'b0;
062
063 if( count<=8'd15 )
064 aud_sdo=tag[15-count];
065 else if( count<=8'd35 )
066 aud_sdo=control[n_frame][35-count]; // Adding(change)
067 else if( count<=8'd55 )
068 aud_sdo=control[n_frame+4][55-count]; // Adding(change)
069 else if( count<=8'd75 )
070 aud_sdo = wave[75-count];
071 else if( count<=8'd95 )
072 aud_sdo = wave[95-count];
073 else
074 aud_sdo = 1'b0;
075
076 //--- sdata_in --- // Adding
077 if( count>=8'd57 && count<=8'd76 ) // Adding
078 wave_in0[76-count] = aud_sdi; // Adding
079 else if( count<=8'd96 ) // Adding
080 wave_in1[96-count] = aud_sdi; // Adding
081
082 if( count==8'd255 )begin
083 // Sine-wave generator
084 y0 = y1*a1 + y2*a2;
085 y2 = y1;
086 y1 = { y0[31], y0[28:14] };
087 if(sw[1]==1'b1)
088 wave = y0[31:12];
089 else
090 //wave = y0[31]==1'b1 ? 20'h_f_0000 : 20'h_0_ffff;
091 wave = wave_in0; // Adding(change)
092
093 n_frame = n_frame+1'b1;
094 end
095
096 count = count + 1'b1;
097 c = c + 1'b1;
098 end
099 endmodule
// Addingが音声出力プログラムからの機能追加部分
<接続>
①黒色ヘッドホン端子にヘッドホンまたはパソコンの入力端子を接続
②青色Line入力端子に音楽プレーヤを接続
<操作手順>
①合成後ATLYSに*.bitファイルをアップロードにより自動実行
②SW0(A10) ON:動作 OFF:非動作
③SW1(A14) ON:440Hz正弦波出力 OFF:スルー動作
基礎が終わったので早速アプリケーションを作りましょう
スループログラムを利用してカラオケプログラムを作成する.原理は簡単で,通常のステレオ音声ではボーカルがセンターに来るように調整されている.音像をセンターに定位させるためには,同位相,同レベルの音声波形をLチャンネルRチャンネル両チャンネルから再生する必要があり,ソロのボーカル楽曲CDはそのようにデザインされている.したがって,バックの演奏はLチャンネルとRチャンネルとで同じ波形はボーカルに比べて少ないので,LチャンネルとRチャンネルの差をとることにより(引き算),ボーカルを消すことができる.
すなわち,thru.vの091行目を次のように変更するとカラオケ音声(センター音消去)になる.
091 wave = wave_in0-wave_in1; // Adding(change)
次にFIRプログラムをFPGAに組み込んでみよう.作成するFIRフィルタを図5-5に示す.
ディジタル信号処理の前半の授業で既出なので,詳しい説明は省くが,FPGAプログラムでは,入力X0がAICよりFPGAに送られるごとに,FIRの計算を行う.計算が完了しyが求まればそれをAICに送り出力とする.計算が終わったら,X2→X3,X1→X2,X0→X1の順にデータをコピーする.それを際限なく繰り返すことにより音声の連続データに対応する.図では,3次の場合について説明したが,次数は,レジスタTと乗算器aを増やしていくことで,性能の良い高次のものを作ることができる.
ここまでのヒントとディジタル信号処理の前半の授業を元に,まずは答えを見ないで自分でFIRプログラムをFPGAで組んでみよう.thru.vのwave_in0, wave_in1を利用して,レジスタT(X0~Xn)を記述は容易だが乗算器の記述は難しい.各自で独自に工夫してやってほしいのは山々だが,時間も所なので回答を示そう.以下に示すのは60次のFIRフィルタである.
225 //------ fir ------
226 module fir(
227 input wire clock, // clock
228 input wire reset, // reset
229 input wire request, // request
230 input wire signed[15:0] x_in, // input wave from main
231 output reg signed[15:0] y_out,// output wave for main
232 input wire ssw // switch input
233 );
234
235 reg[5:0] q; // 6 bits counter 0 to 63
236 reg signed[15:0] x[0:60]; // 60 dimensions
237 reg signed[15:0] y;
238
239 always @( posedge clock or posedge reset ) begin
240 if( reset==1'b1 )
241 begin
242 q = 6'd0;
243 y = 16'd0;
244 end
245 else //if( clock==1'b1 ) <- NG NG NG
246 if( request==1'b1 )
247 begin
248 if( q==6'd0 )
249 begin
250 x[0] = x_in;
251 y_out = 16'd0;
252 q = q + 6'd1;
253 end
254 else if( q<=6'd61 ) // q==1 to 61
255 begin
256 if( ssw==1'b1 )
257 y = y + x[q-1]*k2[q-1]/32768; // 16bit;
258 else
259 y = y + x[q-1]*k3[q-1]/32768; // 16bit;
260 q = q + 6'd1;
261 end
262 else if( q==6'd62 )
263 begin : block_name0000
264 integer i;
265 for( i=59; i>=0; i=i-1 )
266 x[i+1] = x[i]; // shift copy
267 y_out = y;
268 q = 6'd63; // q=6'd63 means a paud state
269 end
270 end
271 else
272 q = 6'd0;
273 end
274 endmodule
227: mainと同じClock信号を入れる
228:229: resetとrequset信号で制御
230: 16ビットの入力信号
231: 16ビットの出力信号
235: 6ビットカウンターでモジュールを制御
236: 配列x[]でメモリTを表現
237: yに合計を置く
239: clockとresetで制御する
240: resetが1のとき変数qとyを0リセットする.
245: clockが立ち上がったとき実行
246: requesetが1のとき以下を実行
248: カウンタqが0のとき
250: 入力x_inをx[0]に格納
251: y_outを0リセット
252: カウンタqアップ
254: カウンタqが1~61のとき
256: スイッチsswがONのとき
257: 係数k2[]でFIR計算
258: スイッチsswがOFFのとき
259: 係数k3[]でFIR計算
260: カウンタqアップ
262: カウンタqが62のとき
265:266: x[60]←x[59],x[59]←x[58]... x[2]←x[1],x[1]←x[0]の順にメモリ内容をシフトコピー
267: 合計のyをy_outに格納
268: カウンタqを63にしてこの後,モジュールを休止する
271: requestが0のとき,カウンタqを0リセットする
このモジュールは,音声入出力モジュールと同一クロックで動作する.次数は60次でメモリx[]もフィルタ係数k2[],k3[]も0~60の添え字を使って表される.重要な部分は2点あり1点目は257,259行目のフィルタ計算で,図3の乗算と加算をクロックカウント数qに伴って行う.257行目と259行目のどちらを実行するかでフィルタの特性を変えることができ,これはsswの値で制御する.もう1点は264~266行目のメモリ内容のシフトコピー動作を行う.フィルタ計算が終わった後に,メモリの内容を以下のように移動し,x[0]に次の値を迎える準備をする.このシフトコピーがメモリTのクロックが立ち上がり,メモリの値が入力値に入れ替わる動作にあたる.
x[60]←x[59], x[59]←x[58], ... , x[2]←x[1], x[1]←x[0]
000 `timescale 1ns / 1ps
020
021 module main_thru_and_fir(
022 input wire aud_bit_clk,
023 output wire aud_sdo,
024 output wire aud_sync,
025 output wire aud_reset,
026 input wire aud_sdi,
027 input wire[2:0] sw,
028 output wire[7:0] led );
029
030 wire signed[15:0] in_fir, out_fir;
031 wire request_fir;
032
033 // ".name" is a variable in the instance.
034 thru_and_fir i0( // main control and aic control
035 .aud_bit_clk( aud_bit_clk ), // clock from aic
036 .aud_sdo( aud_sdo ), // output for aic
037 .aud_sync( aud_sync ), // sync for aic
038 .aud_reset( aud_reset ) , // reset for aic
039 .aud_sdi( aud_sdi ), // input from aic
040
041 .reset_fir( reset_fir ), // reset for fir
042 .in_fir( in_fir ), // input for fir
043 .request_fir( request_fir ), // request for fir
044 .out_fir( out_fir ), // output from fir
045
046 .sw( sw[1:0] ), // switch signal input
047 .led( led ) // LED display output
048 );
049 fir i1( // fir implementation
050 .clock( aud_bit_clk ), // clock for fir
051 .reset( reset_fir ), // reset for fir
052 .request( request_fir ), // request for fir
053 .x_in( in_fir ), // input for fir
054 .y_out( out_fir ), // output from fir
055 .ssw( sw[2] ) // switch signal input for fir
056 );
057 endmodule
058
059
060 module thru_and_fir(
061 input wire aud_bit_clk, // AIC control ********************************
062 output reg aud_sdo,
063 output reg aud_sync,
064 output wire aud_reset,
065 input wire aud_sdi,
066
067 output reg reset_fir, // fir control ********************************
068 output reg signed[15:0] in_fir,
069 output reg request_fir,
070 input wire signed[15:0] out_fir,
071
072 input wire[1:0] sw, // etc control ********************************
073 output wire[7:0] led
074 );
075
076 reg signed[15:0] wave_tmp;
077 reg signed[19:0] wave_in0, wave_in1;
078
079 assign aud_reset = 1'b1;//sw[0];
080 assign led = c[23:16];
081
082 reg[7:0] count=8'd255;
083 reg[1:0] n_frame=2'b11;
084
085 reg[23:0] c=24'h000000;
086 reg signed[19:0] wave=20'h00000;
087 reg signed[19:0] wave_rand;
088 reg[31:0] r=32'd0;
089
090 parameter tag = 16'b_1111_1000_0000_0000;
091
092 parameter[19:0] control[0:7] = {
093 // add0, add1, add2, add3, data0, data1, data2, data3
094 20'h_04_000, 20'h_18_000, 20'h_1A_000, 20'h_1C_000,
095 20'h_0000_0, 20'h_0808_0, 20'h_0404_0, 20'h_0000_0
096 };
097
098 // Sine-wave generator (Preparation)
099 reg signed[31:0] y0;
100 reg signed[15:0] y1 = 16'h_03af, y2 = 16'h_0000;
101 parameter signed[15:0] a1 = 16'h_7fc9, a2 = 16'h_c000;
102
103 wire clk = (sw[0]==1'b1 ? aud_bit_clk : 1'b0);
104 always @( posedge clk ) begin
105 if( count==8'd255 || count<=8'd14 )
106 aud_sync=1'b1;
107 else
108 aud_sync=1'b0;
109
110 if( count<=8'd15 )
111 aud_sdo=tag[15-count];
112 else if( count<=8'd35 )
113 aud_sdo=control[n_frame][35-count];
114 else if( count<=8'd55 )
115 aud_sdo=control[n_frame+4][55-count];
116 else if( count<=8'd75 )
117 aud_sdo = wave[75-count];
118 else if( count<=8'd95 )
119 aud_sdo = wave[95-count];
120 else
121 aud_sdo = 1'b0;
122
123 //--- sdata_in ---
124 if( count>=8'd57 && count<=8'd76 )
125 wave_in0[76-count] = aud_sdi;
126 else if( count<=8'd96 )
127 wave_in1[96-count] = aud_sdi;
128
129
130 if( count==8'd150 )begin
131 r=r*214013+2531011;
132 wave_rand = {r[16],r[16],r[16],r[16:0]};
133 end
134
135 //------ fir filter drive------------
136 if( count==152 )
137 reset_fir = 1'b1; // reset on
138 else if (count==153)
139 reset_fir = 1'b0; // reset off
140
141 else if (count==154)
142 in_fir <= wave_in0[19:4]; // input in_fir
143 //in_fir <= wave_rand[19:4]; // input in_fir
144 else if (count==155)
145 request_fir = 1'b1; // request on
146
147 else if (count>=220 && count<= 230 )
148 if( request_fir==1'b1 )
149 begin
150 wave_tmp = out_fir;
151 request_fir = 1'b0; // request off
152 end
153 //------ end of fir filter drive------------
154
155 if( count==8'd255 )begin
156 // Sine-wave generator
157 y0 = y1*a1 + y2*a2;
158 y2 = y1;
159 y1 = { y0[31], y0[28:14] };
160
161 if(sw[1]==1'b1)
162 wave = {wave_tmp,4'h0};
163 else
164 wave = wave_in0;
165 //wave = wave_rand;
166
167 n_frame = n_frame+1'b1;
168 end
169
170 count = count + 1'b1;
171 c = c + 1'b1;
172 end
173 endmodule
174
175
176
<プログラムの概要と注意点>
021~057行目:モジュールmain_thru_and_firは,2つのモジュールthru_and_firとfirをつなぐとともに外部信号とのやりとりを行う.このモジュールの機能は結線のみである.「.」つきの信号名(.aud_bit_clkなど)は呼び出される側のモジュール上で定義した信号名であり,「.」なしの信号名は呼び出しが側のモジュール上の信号名である.名前自体は同じでも良い.
022~028行目:FPGAの外部(AICなど)とのやりとりをする信号名を定義する.
060~176行目:モジュールthru_and_firは,第一章で作成したスループログラムにモジュールFIRのコントロール部分を追加したものである.
135~153行目:スループログラムからの追加部分でFIRフィルタをコントロールする.
142~143行目:FIRフィルタにデータを渡す.コメントアウトされている143行目を有効にするとプログラムで作成したランダム信号のデータを渡すことができる.
161~165行目:スルー動作とフィルタ動作を切り替える.
200 parameter signed[15:0] k3[0:60]={ // LPF
201 // [h,hm,fr]=wfir('lp',61,[0.0625 0],'kr',[1 0]);
202 16'hff3e,16'hfef6,16'hfed1,16'hfed9,16'hff12,16'hff79,16'h0000,16'h0097,
203 16'h0128,16'h019a,16'h01d7,16'h01ce,16'h0179,16'h00da,16'h0000,16'hff05,
204 16'hfe0b,16'hfd3a,16'hfcba,16'hfcaf,16'hfd31,16'hfe4e,16'h0000,16'h0233,
205 16'h04c2,16'h077b,16'h0a25,16'h0c84,16'h0e64,16'h0f96,16'h1000,16'h0f96,
206 16'h0e64,16'h0c84,16'h0a25,16'h077b,16'h04c2,16'h0233,16'h0000,16'hfe4e,
207 16'hfd31,16'hfcaf,16'hfcba,16'hfd3a,16'hfe0b,16'hff05,16'h0000,16'h00da,
208 16'h0179,16'h01ce,16'h01d7,16'h019a,16'h0128,16'h0097,16'h0000,16'hff79,
209 16'hff12,16'hfed9,16'hfed1,16'hfef6,16'hff3e };
210
211 parameter signed[15:0] k2[0:60]={ // HPF
212 //[h,hm,fr]=wfir('hp',61,[0.0625 0],'kr',[1 0]); // plot2d(fr,hm);
213 //mprintf( "16h%c%04x\,\n", char(ones(61,1)*39), bitand( int(h'*32768), ones(61,1)*65535 ) );
214 16'h00c2,16'h010a,16'h012f,16'h0127,16'h00ee,16'h0087,16'h0000,16'hff69,
215 16'hfed8,16'hfe66,16'hfe29,16'hfe32,16'hfe87,16'hff26,16'h0000,16'h00fb,
216 16'h01f5,16'h02c6,16'h0346,16'h0351,16'h02cf,16'h01b2,16'h0000,16'hfdcd,
217 16'hfb3e,16'hf885,16'hf5db,16'hf37c,16'hf19c,16'hf06a,16'h7000,16'hf06a,
218 16'hf19c,16'hf37c,16'hf5db,16'hf885,16'hfb3e,16'hfdcd,16'h0000,16'h01b2,
219 16'h02cf,16'h0351,16'h0346,16'h02c6,16'h01f5,16'h00fb,16'h0000,16'hff26,
220 16'hfe87,16'hfe32,16'hfe29,16'hfe66,16'hfed8,16'hff69,16'h0000,16'h0087,
221 16'h00ee,16'h0127,16'h012f,16'h010a,16'h00c2};
リスト3,4,5を入力して,合成するとFIRフィルタを実行できる.接続と動作は次の通り.
<接続>
①黒色ヘッドホン端子にヘッドホンまたは,パソコンの入力端子をつなぐ.
②青色Line入力端子に音楽プレーヤを接続.
<動作>
①合成後ATLYSに*.bitファイルをアップロードにより自動実行
②SW0(A10) ON:動作 OFF:非動作
③SW1(A14) ON:フィルタ動作 OFF:スルー動作
③SW2(C14) ON:HPF(K2) OFF:LPF(K3)
FIRフィルタは動作しただろうか?
音楽プレーヤからの出力は周波数特性を確認しにくいので,代わりに,プログラム上で生成したホワイトノイズを入力して周波数特性を確認する.
ATLYSのリファレンス・マニュアルはhttp://www.digilentinc.com/Data/Products/ATLYS/ATLYS_rm.pdfからダウンロードできる.(または,「ATLYS reference」でインターネット検索.)
AIC(テキサ・スインスツルメント社製LM4550B)のデータシートはhttp://www.ti.com/lit/ds/snas276f/snas276f.pdfからダウンロードできる.サイトが不通の場合は,改めて 「ti lm4550b datasheet」をキーワードにしてインターネット検索してほしい.ダウンロードしたデータシートはPDF表示プログラム(Adobe Reader)から利用できる.頻繁に利用する箇所についてはプリントアウトした方が良いかもしれない.
以上,今回はAICの使い方でした.
さらに進んだアプリケーションを作れるよう研鑽して下さい.
ssatoh@
足立工科大学 工学部 情報通信工学科