第5回 音IC(AIC)制御
公式
\[ y = ax^2+bx+c \]そこまで戻るか?

内 容

本日は音IC(AIC)制御について述べます. 皆さんも,パソコンのオーディを機能を使って,音楽をダウンロードしたり,音楽を聴いたりしていると思います.音楽を聴くときに働いているのがAICです.主な仕事はパソコンのディジタル信号をアナログ信号に変換し増幅し,ヘッドホンを通して,人間の聞こえる音を出すことです.また,録音のためには,マイクから入力したアナログ信号をパソコンが処理できるディジタル信号に変えたりもします.

もちろんインターネット上で扱える音データもこのディジタル信号です.最近はこれを利用して,適当に圧縮して通話(Skype,LINE)を行ったりもしています.このAIC,Atlys上にも搭載されていて,FPGAから自由に制御できます.ディジタル時計の延長線上で音の制御もできるのです.

5-1 まず,音を出す

『そうだ,音を出そう』

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ファイルが起動して音が再生される.どのような音が出るだろうか?

[再掲]新しいプロジェクト,Verilogファイルひな形,制約ファイルひな形の作成

  1. ISEを起動する.
  2. File → New Project → New Project Wizardが表示される.
  3. Name: に適当なプロジェクト名を入力 → Next.
  4. Project Settingで以下を確認する.
  5. Project→New Source→New Source Wizardが表示される.
  6. 左側でVerilog Moduleを選択し,適当なファイル名を入力→Next→Next→FinishでVerilogプログラムファイルのひな形が表示される(*.v).
  7. さらに⑤の操作の後,左側でImplementation Constraints File(実装制約ファイル)を選択し,適当なファイル名を入力→Next→Finishで制約ファイルのひな形が表示される(*.ucf).
  8. ここで,いったんISEを終了 File → Exit(プロジェクトを保存する.一回終了保存しないと動作が異常なことがある.)

リスト 4-1 audio_out.vソース


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

リスト 4-2 audio_out.ucfソース


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";

いくつか,入力→合成→ファイル転送を行っている時に起きる代表的なトラブルを紹介しよう.もしかすると,これに該当する読者もいるかもしれない.

  1. ケース1.合成(Synthesize - XST)がエラーで止まる → *.vファイル打ち込みミス
  2. ケース2.実装(Implement Design)がエラーで止まる → *.ucfファイル打ち込みミス
  3. ケース3.転送できない → USB接続端子違い,本体電源が入っていない
  4. ケース4.音が出ない → 打ち込みミス,転送したプログラムが別の物, ヘットホン接続ミス

『そしてお,お,音がでた』

「ピー」という音が再生されただろうか?これは440Hzの正弦波で物理や電気で良く登場する波で1秒間に440回振動する.正弦波は音,音楽,物理,電気の基本である.音楽ではピアノ鍵盤の中央から少し左の「ラ」の音がこれにあたる.NHK等の時報「ピッ・ピッ・ピッ・ポーン」の3こある「ピッ」がこれだ.(地上デジタル放送では時間遅れの関係で放送されなくなった.)

補足

プログラムを手入力したみなさんなら解読しながら打ち込んだと思うので,当然,スライドスイッチA10(sw[0]),D14(sw[1])の動作については,予想がついたと思うがsw[0]はAICのリセット端子につながっていて,0(OFF)のときAICは動かない.またsw[1]は1(ON)で正弦波,0(OFF)で方形波を出力する.(実際はDA変換後のローパスフィルタの関係で正確な方形波にはならない.)

時報の絵

図5-1 ラジオの時報
図5-2 時報の正弦波(440Hzの場合の周期を計算して書き入れて下さい.)

音を見る!

パソコンの音声入力を利用したFFTスペクトルアナライザ(http://efu.jp.net/soft/ws/ws.htmlページの下の方からWS151.ZIPをダウンロード・インストール)を利用して,音を見てみよう.手順は以下の通り.①ATLYSの音声出力をパソコンの音声入力につなぐ.②WS.EXEのアイコンをダブルクリックして実行する.③録音ボタン(赤丸のボタン)をクリックする.④入力波形が表示され,周波数スペクトルが表示される.

音を変えてみよう

周波数を変更してみよう.提供したプログラムは440Hzの正弦波と方形波をsw[1](A10)のON/OFFで切り替えて出力するように書かれている.周波数は,31,32行目のy1,a1の値で決まるので,以下のように変更してみよう.どのような音が出力されるだろうか?周波数スペクトル表示して測定しよう.

変更1


	変更前			変更後
	y1 = 16'h_03af	y1 = 16'h_1090;
	a1 = 16'h_7fcf	a1 = 16'h_7ba3;

ヒント 周波数の設定値

発振したい周波数を\(f\)[Hz],サンプリング周波数を\(f_s\)[Hz],円周率\(\pi\)として

\(y_1=\sin(2\pi f/f_s) \times 2^{14}\)
\(y_2=0\)
\(a_1=2\cos(2\pi f/f_s) \times 2^{14}\)
\(a_2=-1 \times 2^{14}\)
・・・式(1)

sin,cos関数から得られた実数のままでは使えないので16,384(=2^14)を掛けて,16ビット符号付き整数(2進数の左端の桁から2桁目と3桁目の間に少数点がある符号付き固定小数点表示実数00.00000000000000)にする.fに希望する周波数を代入し,式の結果を用いてy1,a1を変更すれば必要な周波数の正弦波を得られる.

変更2 疑似ホワイトノイズの発生


 33行目に追加
	reg[31:0] r = 32'h0;
 61行目に入れ替え
	begin
		r=r*214013+2531011;
		wave = r[19:0];
	end;

以上を変更して,合成して,ATLYSで実行すると疑似ホワイトノイズを発生できる.ホワイトノイズは,周波数特性が一定でフィルタの周波数特性を観測するときにも用いられる(後述).FM放送帯で放送がない周波数で聴取できる「ザー」というノイズ音声である.

課題1

スライドスイッチを切り替えることにより,ヘッドホンのLチャンネルのみ,または,Rチャンネルのみから音声が出力されるようにする.

ヒント

プログラムの46,47行目がLチャンネル出力,48,49行目がRチャンネル出力である.

ATLYSに音を入力しよう

 音声出力が確認できたところで,次は用意してもらった音源(iPodなどデジタルプレーヤー)をライン入力端子(青色)に入力して,ATLYSに音声を入力しよう.リスト1の*.vファイルの何点かを変更することで音声入力機能が追加できる.

リスト1からの変更点


① 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;	

問題2

  1. 音声の入力を完成させるためには,次の一行をプログラムのどこかに追加(または変更)し,入力された音データを出力データとして渡す必要がある.それはプログラム中のどこか?検討せよ.
  2. 
    	wave = wave_in0;
    
  3. また,moduleの引数として渡したaud_sdiを制約ファイル(*.ucf)上に定義する必要がある.検討してaud_sdiを設定するNET文を完成せよ.

5-2 AIC制御

『FPGAとAICとの信号はどのようにやりとりされている』

ATLYSのリファレンスマニュアルP14-15を見るとATLYS上のAICの解説がある.FPGAとAIC間の「つなぎ」(接続)について必要部分を抜粋すると次のようになる.

表 5-1 FPGA,AIC間の信号のやりとり
信号名FPGA
PIN
信号方向
FPGA←→AIC
AIC
PIN
備考
AUD_BIT_CLKL13  ←6クロック信号
AUD_SDIT18  ←8音声入力データ
AUD_SDON16  →5音声出力データ
AUD_SYNCU17  →10同期信号(アクティブHi)
AUD_RESETT17  →11リセット信号(アクティブLow)


[ポイントA] ①のクロック信号は,AICからFPGAに送られ,AICの操作のための信号はこのクロックに同期させる必要がある.
[ポイントB] 音声を出力する場合は③を通してディジタルデータをFPGAからAICに送る.
[ポイントC] 外部からマイクなどで音声を入力する場合は②を通してディジタルデータをAICからFPGAに送る
[ポイントD] リセット信号でAICを初期化し,同期信号でデータの先頭をAICに知らせる.

AC LINK OUTPUT FRAMES: SDATA_OUT

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スロットは今回は使用しない.)

AC LINK INPUT FRAMES: SDATA_IN

図中3行目がAICからFPGAに送られるデータ(AUD_SDI)で第3スロットがPCM LEFT,第4スレットがPCM RIGHTである.

スロット0のTAG のみ16ビット,他のスロットは20ビットの長さがあり,1フレームのビット数は16+20×12=256ビットである.

図5-3 シリアルインターフェース(LM4550Bデータシートより転載)

データビットのタイミング

図5-4からわかるように,フレーム内のビットのHi/LowはBIT_CLKの立ち下がりでAIC側が判断する.従ってデータビットは,BIT_CLKの立ち上がり後に変更し,立ち下がり時には安定していなければならない.

図5-4 出力フレーム(LM4550Bデータシートより転載)

参考

LM4550Bのブロックダイアグラム(LM4550Bデータシートより)
LM4550Bの設定レジスタマップ(LM4550Bデータシートより)

リスト 5-3 thru.v ソース


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:スルー動作

5-3 アプリケーションプログラム

基礎が終わったので早速アプリケーションを作りましょう

5-3-1 カラオケプログラム

スループログラムを利用してカラオケプログラムを作成する.原理は簡単で,通常のステレオ音声ではボーカルがセンターに来るように調整されている.音像をセンターに定位させるためには,同位相,同レベルの音声波形をLチャンネルRチャンネル両チャンネルから再生する必要があり,ソロのボーカル楽曲CDはそのようにデザインされている.したがって,バックの演奏はLチャンネルとRチャンネルとで同じ波形はボーカルに比べて少ないので,LチャンネルとRチャンネルの差をとることにより(引き算),ボーカルを消すことができる.

すなわち,thru.vの091行目を次のように変更するとカラオケ音声(センター音消去)になる.

091                wave = wave_in0-wave_in1;        // Adding(change)

5-3-2 FIRフィルタプログラム

次にFIRプログラムをFPGAに組み込んでみよう.作成するFIRフィルタを図5-5に示す.

図5-5 3次のFIRフィルタ

ディジタル信号処理の前半の授業で既出なので,詳しい説明は省くが,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フィルタである.

リスト 5-4 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]

リスト 5-5 FIR呼び出しメイン部分ソース

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行目:スルー動作とフィルタ動作を切り替える.

リスト 5-6 FIRフィルタ係数部分ソース

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フィルタは動作しただろうか?

問題1

音楽プレーヤからの出力は周波数特性を確認しにくいので,代わりに,プログラム上で生成したホワイトノイズを入力して周波数特性を確認する.

参考

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@

足立工科大学 工学部 情報通信工学科