- ZYBOでシンセサイザー作成(1)LチカとHelloWorld
- ZYBOでシンセサイザー作成(2)オーディオコーデックの使用
- ZYBOでシンセサイザー作成(3)波形テーブル(BRAM)による正弦波の合成
- ZYBOでシンセサイザー作成(4)パラシリ変換器の作成
- ZYBOでシンセサイザー作成(5)音を出力する
前回と前々回でパラシリ変換器(sin_gen)と正弦波生成 IP(para2serial) が作成できました。今回は、これらを組み合わせて、ZYBOから音を出力したいと思います。
環境
システムの構成
システムの構成を図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)。
次に、「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
Clocking Wizard は以下のように設定します。
- Input Frequency:50 MHz
- Output Freq:96 MHz
- locked:チェックOFF
Binary Counter は以下のように設定します。
- Output Width:2
Slice は以下のように設定します。
- Din Width:2
- Din From:1
- Din Down To:1
- Dout Width:1
配線は図7のように接続します。
配線ができたら、「Validate Design」で回路チェックをして、「Create HDL Wrapper...」でラッパーを更新します。
制約ファイルについては、「ac_bclk」と「ac_mclk」と「ac_pbdat」と「ac_pblrc」の行のコメントアウトを外します。
##Audio Codec set_property -dict { PACKAGE_PIN R19 IOSTANDARD LVCMOS33 } [get_ports { ac_bclk }]; #IO_0_34 Sch=ac_bclk set_property -dict { PACKAGE_PIN R17 IOSTANDARD LVCMOS33 } [get_ports { ac_mclk }]; #IO_L19N_T3_VREF_34 Sch=ac_mclk set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { ac_muten }]; #IO_L23N_T3_34 Sch=ac_muten set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { ac_pbdat }]; #IO_L20N_T3_34 Sch=ac_pbdat set_property -dict { PACKAGE_PIN T19 IOSTANDARD LVCMOS33 } [get_ports { ac_pblrc }]; #IO_25_34 Sch=ac_pblrc #set_property -dict { PACKAGE_PIN R16 IOSTANDARD LVCMOS33 } [get_ports { ac_recdat }]; #IO_L19P_T3_34 Sch=ac_recdat #set_property -dict { PACKAGE_PIN Y18 IOSTANDARD LVCMOS33 } [get_ports { ac_reclrc }]; #IO_L17P_T2_34 Sch=ac_reclrc set_property -dict { PACKAGE_PIN N18 IOSTANDARD LVCMOS33 } [get_ports { iic_scl_io }]; #IO_L13P_T2_MRCC_34 Sch=ac_scl set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { iic_sda_io }]; #IO_L23P_T3_34 Sch=ac_sda
制約ファイルを保存したら、「Generate Bitstream」をします。
非同期クロック間の制約
「Generate Bitstream」が完了すると、「write_bitstream_Complete」が出ると思いますが、「Open Implemented Design」をして、「Timing」タブの Worst Negative Slack を確認すると負の値になっています(図8)。また、Critical Warning もあるので好ましくありません。
原因は、非同期クロック間で不必要なタイミング解析を行っているからです。MMCMに入力される50MHzのクロックとMMCMから出力される96MHzのクロックは非同期ですが、今回は同期しているとしてタイミング解析がされてしまいました。
非同期クロック間でタイミング解析を行わないように制約を与えます。「Timing」タブ→「Inter-Clock Paths」を選択して、クロックのペアを右クリックしてから「Set Clock Groups...」で設定できます(図9)。
「Set Clock Groups」では、「Group name」を記入して、「OK」を押します。私の場合は、「Group name」を「50MHz_and_96MHz」としました(図10)。
設定を終えたら、「Timing Constraints」タブの「Apply」で設定を適用します(図11)。
制約を適用できたら、再び「Generate Bitstream」でビットストリームを作成します。
制約ファイルを保存するか聞かれますので、「Save」を押します(図12)。
次に、「論理合成と配置配線が古くなりますよ」という警告が出ますが、そのまま「OK」でいいです(図13)。
制約ファイルを更新するか、新しく作成するか聞かれますので、「Update」を押して更新します(図14)。
「Select an existing file」が選択されていることと制約ファイル名が正しいことを確認して、「OK」を押します(図15)。
あとはいつもの設定画面が出ますので入力すると、「Generate Bitsteam」が始まります。
「write_bitstream_Complete」が出たら、一応、タイミングを満たしているか確認します。「Reload」を押して、配置配線後のデザインをリロードします(図16)。
「Timing」タブから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"); // Codec Initialization status = CodecInit(&Iic); if(status != XST_SUCCESS) { xil_printf("Error Codec Initialization"); return XST_FAILURE; } // AXI GPIO Initialization 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; } // Port Direction XGpio_SetDataDirection(&Gpio0, 1, 0x0); // output XGpio_SetDataDirection(&Gpio1, 1, 0x00); // output // Output Level XGpio_DiscreteWrite(&Gpio0, 1, 0xF); // All lights up XGpio_DiscreteWrite(&Gpio1, 1, 0x1B8); // Set to 440Hz xil_printf("IIC Finished\n"); return XST_SUCCESS; } // Write Audio Codec Register int CodecWrite(XIic* Iic, u8 Address, u16 data){ u8 Device_Address = 0x1A; // Device ID UINTPTR BaseAddress = Iic->BaseAddress; // AXI IIC BaseAddress int num; // Number of Data sent // set write date u8 WR_data[2]; Address = ((Address<<1) & 0xFE); WR_data[0] = Address + ((data>>8)&0x01); WR_data[1] = (data&0xFF); // send data 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; } // Audio Codec Initialization XStatus CodecInit(XIic* Iic){ int status; // Initializes XIic instance. status = XIic_Initialize(Iic, XPAR_IIC_0_DEVICE_ID); if (status != XST_SUCCESS){ return XST_FAILURE; } // Codec register settings CodecWrite(Iic, R6_POWER_MGMT, 0x77); // power on 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); // wait for charging capacity on the VMID pin t=C*25000/3.5 CodecWrite(Iic, R9_ACTIVE, 0x01); // digital core active CodecWrite(Iic, R6_POWER_MGMT, 0x67); return XST_SUCCESS; }
AXI GPIOの設定を追加して、コーデックのレジスタの値を変更しています。コーデックのレジスタ設定で重要な箇所は以下です。
- DIGITAL AUDIO I/F Register (0x07) の設定
- Sampling Rate Register (0x08) の設定
- MCLK=24MHzでDACサンプリングレートが48kHzとなるように設定
ビルドして、実行するとLEDは点灯しますが、音はでないと思います。ミュートスイッチによってミュートされていますので、スイッチを図18のようにすることでミュートがOFFになり、音が出力されます。
上手くできると以下のような音が出ます。出力される波形とスペクトログラムは図19のようになります。
440Hz以外にも成分があると思いますが、おそらく原因は正弦波生成 IP (sin_gen) だと思います。sin_genについてはあとで作りなおしたいと思います。
変な音がでる
上の例だと出力される音が綺麗なのですが、これは良い例です。7,8割は上手くいかず、以下のような音が出ます(イヤホンでは聴かないほうがいいです)。出力される波形とスペクトログラムは図20のようになります。
原因はいまだわかっていないです。プログラムの実行順番を変えたりしたら治ったり、プログラムを元に戻しても変にはならなかったりして、原因がわかりません。ロジックアナライザで確認しても、シリアルデータはフォーマット通りのため、FPGAの外の問題なのかもしれません。
おわりに
音の出力が上手くいかず気持ち悪いですが、他のことをやりながら原因をつきとめようと思います。次の記事では、フィルタの作成を行いたいと思います。