前回と前々回でパラシリ変換器(sin_gen)と正弦波生成 IP(para2serial) が作成できました。今回は、これらを組み合わせて、ZYBOから音を出力したいと思います。
環境
パソコン:Windows10 64 bit
Vivado 2020.2 をインストール
Xilinx Vitis 2020.2 をインストール
ボード:Zybo Z7-20
出力デバイス :スピーカー
システムの構成
システムの構成を図1に示します。
図1:システムの構成
データをスピーカーに出力する手順は以下です。
「AXI GPIO」で正弦波生成 IPに周波数値を与える
正弦波生成 IPの出力値をパラシリ変換 IPでシリアルに変換する
コーデックでシリアルデータをDA変換してスピーカーに出力
また、MMCMで50MHz から96MHzのクロックを生成して、カウンタを用いて4分周することで24MHzのMCLKを生成します。コーデックが12MHzのBCLKを生成しますので、BCLKに同期してパラシリ変換 IPでデータを出力します。
IP インテグレータの編集
IP インテグレータで IP どうしの 接続を行います。ZYBOでシンセサイザー作成(2)オーディオコーデックの使用 のプロジェクトzybo_synthesizerを編集していきます。
まず、プロジェクトのリポジトリ リストに「ip_repo_synth」を追加します。「PROJECT MANAGER」→「Settings」で「Settings」画面を開き、「Project Settings」→「IP」→「Repository」の「+」で「ip_repo_synth」を追加して、「OK」を押します(図1)。
図1:IPリポジトリ の追加
次に、「Add IP」から以下の IP を追加します。
sin_gen_v1_0
para2serial_v1_0
Clocking Wizard
AXI GPIO
Binary Counter
Slice
AXI GPIO は以下のように設定します(図2)。
All Outputs:チェックON
GPIO Width:15
図2:AXI GPIOの設定
Clocking Wizard は以下のように設定します。
Input Frequency:50 MHz
Output Freq:96 MHz
locked:チェックOFF
図3:Clocking Wizardの設定(Clocking Options)
図4:Clocking Wizard の設定(Output Clocks)
Binary Counter は以下のように設定します。
図5:Binary Counterの設定
Slice は以下のように設定します。
Din Width:2
Din From:1
Din Down To:1
Dout Width:1
図6:Sliceの設定
配線は図7のように接続します。
図7:配線後のIPインテグレーション
配線ができたら、「Validate Design」で回路チェックをして、「Create HDL Wrapper...」でラッパーを更新します。
制約ファイルについては、「ac_bclk」と「ac_mclk」と「ac_pbdat」と「ac_pblrc」の行のコメントアウト を外します。
set_property -dict { PACKAGE_PIN R19 IOSTANDARD LVCMOS33 } [get_ports { ac_bclk }];
set_property -dict { PACKAGE_PIN R17 IOSTANDARD LVCMOS33 } [get_ports { ac_mclk }];
set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { ac_muten }];
set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { ac_pbdat }];
set_property -dict { PACKAGE_PIN T19 IOSTANDARD LVCMOS33 } [get_ports { ac_pblrc }];
set_property -dict { PACKAGE_PIN N18 IOSTANDARD LVCMOS33 } [get_ports { iic_scl_io }];
set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { iic_sda_io }];
制約ファイルを保存したら、「Generate Bitstream」をします。
非同期クロック間の制約
「Generate Bitstream」が完了すると、「write_bitstream_Complete」が出ると思いますが、「Open Implemented Design」をして、「Timing」タブの Worst Negative Slack を確認すると負の値になっています(図8)。また、Critical Warning もあるので好ましくありません。
図8:Worst Negative Slack の値
原因は、非同期クロック間で不必要なタイミング解析 を行っているからです。MMCMに入力される50MHz のクロックとMMCMから出力される96MHzのクロックは非同期ですが、今回は同期しているとしてタイミング解析がされてしまいました。
非同期クロック間でタイミング解析を行わないように制約を与えます。「Timing」タブ→「Inter-Clock Paths」を選択して、クロックのペアを右クリックしてから「Set Clock Groups...」で設定できます(図9)。
図9:「Set Clock Groups」を開く
「Set Clock Groups」では、「Group name」を記入して、「OK」を押します。私の場合は、「Group name」を「50MHz _and_96MHz」としました(図10)。
図10:「Set Clock Groups」の設定
設定を終えたら、「Timing Constraints」タブの「Apply」で設定を適用します(図11)。
図11:「Timing Constraints」の設定を「Apply」する
制約を適用できたら、再び「Generate Bitstream」でビットストリームを作成します。
制約ファイルを保存するか聞かれますので、「Save」を押します(図12)。
図12:制約ファイルの保存のダイアログ
次に、「論理合成と配置配線が古くなりますよ」という警告が出ますが、そのまま「OK」でいいです(図13)。
図13:論理合成と配置配線が古くなる警告
制約ファイルを更新するか、新しく作成するか聞かれますので、「Update」を押して更新します(図14)。
図14:制約ファイルを「Update」する
「Select an existing file」が選択されていることと制約ファイル名が正しいことを確認して、「OK」を押します(図15)。
図15:既存の制約ファイルに保存する
あとはいつもの設定画面が出ますので入力すると、「Generate Bitsteam」が始まります。
「write_bitstream_Complete」が出たら、一応、タイミングを満たしているか確認します。「Reload」を押して、配置配線後のデザインをリロードします(図16)。
図16:配置配線後のデザインを「Reload」する
「Timing」タブからWorst Negative Slackを確認すると正の値になっていて、タイミングを満たしていることが確認できます。
図17:Worst Negative Slack の改善
最後に、「Export Hardware...」でハードウェア情報であるXSAファイルを作成します。以上でVivadoでの作業は終わりです。
Vitisでプログラムの作成
Vivadoで「Launch Vitis IDE 」を押して、Vitisを起動します。ハードウェアの更新をVitisに反映させるために、プラットフォームプロジェクトを選択して、マウス右ボタンから「Update Hardware Specification」を実行します。
次に、ソースコード を書き換えます。書き換えたプログラムが以下です。一応、ソースコード はGitHub の「5th」にもあります。
#include "xparameters.h"
#include "xiic.h"
#include "xil_printf.h"
#include "xgpio.h"
#include <sleep.h>
enum adauRegisterAddresses {
R0_LEFT_ADC_VOL = 0x00 ,
R1_RIGHT_ADC_VOL = 0x01 ,
R2_LEFT_DAC_VOL = 0x02 ,
R3_RIGHT_DAC_VOL = 0x03 ,
R4_ANALOG_PATH = 0x04 ,
R5_DIGITAL_PATH = 0x05 ,
R6_POWER_MGMT = 0x06 ,
R7_DIGITAL_IF = 0x07 ,
R8_SAMPLE_RATE = 0x08 ,
R9_ACTIVE = 0x09 ,
R15_SOFTWARE_RESET = 0x0F ,
R16_ALC_CONTROL_1 = 0x10 ,
R17_ALC_CONTROL_2 = 0x11 ,
R18_ALC_CONTROL_2 = 0x12
};
int CodecWrite(XIic*, u8 Address, u16 data);
XStatus CodecInit(XIic *Iic);
int main(void ){
XIic Iic;
XGpio Gpio0, Gpio1;
int status;
xil_printf("IIC Start \n " );
status = CodecInit(&Iic);
if (status != XST_SUCCESS) {
xil_printf("Error Codec Initialization" );
return XST_FAILURE;
}
status = XGpio_Initialize(&Gpio0, XPAR_GPIO_0_DEVICE_ID);
if (status != XST_SUCCESS) {
return XST_FAILURE;
}
status = XGpio_Initialize(&Gpio1, XPAR_GPIO_1_DEVICE_ID);
if (status != XST_SUCCESS){
return XST_FAILURE;
}
XGpio_SetDataDirection(&Gpio0, 1 , 0x0 );
XGpio_SetDataDirection(&Gpio1, 1 , 0x00 );
XGpio_DiscreteWrite(&Gpio0, 1 , 0xF );
XGpio_DiscreteWrite(&Gpio1, 1 , 0x1B8 );
xil_printf("IIC Finished \n " );
return XST_SUCCESS;
}
int CodecWrite(XIic* Iic, u8 Address, u16 data){
u8 Device_Address = 0x1A ;
UINTPTR BaseAddress = Iic->BaseAddress;
int num;
u8 WR_data[2 ];
Address = ((Address<<1 ) & 0xFE );
WR_data[0 ] = Address + ((data>>8 )&0x01 );
WR_data[1 ] = (data&0xFF );
num = XIic_Send(BaseAddress, Device_Address, WR_data, 2 , XIIC_STOP);
if (num!=2 ){
xil_printf("Writing data Failed \r\n " );
return XST_FAILURE;
}
return XST_SUCCESS;
}
XStatus CodecInit(XIic* Iic){
int status;
status = XIic_Initialize(Iic, XPAR_IIC_0_DEVICE_ID);
if (status != XST_SUCCESS){
return XST_FAILURE;
}
CodecWrite(Iic, R6_POWER_MGMT, 0x77 );
CodecWrite(Iic, R0_LEFT_ADC_VOL, 0x97 );
CodecWrite(Iic, R1_RIGHT_ADC_VOL, 0x97 );
CodecWrite(Iic, R2_LEFT_DAC_VOL, 0x79 );
CodecWrite(Iic, R3_RIGHT_DAC_VOL, 0x79 );
CodecWrite(Iic, R4_ANALOG_PATH, 0x10 );
CodecWrite(Iic, R5_DIGITAL_PATH, 0x00 );
CodecWrite(Iic, R7_DIGITAL_IF, 0x53 );
CodecWrite(Iic, R8_SAMPLE_RATE, 0x41 );
usleep(80000 );
CodecWrite(Iic, R9_ACTIVE, 0x01 );
CodecWrite(Iic, R6_POWER_MGMT, 0x67 );
return XST_SUCCESS;
}
AXI GPIOの設定を追加して、コーデックのレジスタ の値を変更しています。コーデックのレジスタ 設定で重要な箇所は以下です。
DIGITAL AUDIO I/F Register (0x07) の設定
MSビットに"1"を設定して、マスターモードにする
LRPビットに"1"を設定して、DSP Submode2にする
WLビットに"00"を設定して、16ビットにする
Formatビットに"11"を設定して、DSP モードにする
Sampling Rate Register (0x08) の設定
MCLK=24MHzでDAC サンプリングレートが48kHzとなるように設定
ビルドして、実行するとLEDは点灯しますが、音はでないと思います。ミュートスイッチによってミュートされていますので、スイッチを図18のようにすることでミュートがOFFになり、音が出力されます。
図18:ミュートをOFF
上手くできると以下のような音が出ます。出力される波形とスペクトログラムは図19のようになります。
図19:出力される波形とスペクトログラム
440Hz以外にも成分があると思いますが、おそらく原因は正弦波生成 IP (sin_gen) だと思います。sin_genについてはあとで作りなおしたいと思います。
変な音がでる
上の例だと出力される音が綺麗なのですが、これは良い例です。7,8割は上手くいかず、以下のような音が出ます(イヤホンでは聴かないほうがいいです)。出力される波形とスペクトログラムは図20のようになります。
図20:変なときの音の波形とスペクトログラム
原因はいまだわかっていないです。プログラムの実行順番を変えたりしたら治ったり、プログラムを元に戻しても変にはならなかったりして、原因がわかりません。ロジックアナライザ で確認しても、シリアルデータはフォーマット通りのため、FPGA の外の問題なのかもしれません。
おわりに
音の出力が上手くいかず気持ち悪いですが、他のことをやりながら原因をつきとめようと思います。次の記事では、フィルタの作成を行いたいと思います。