ZYBOでシンセサイザー作成(5)音を出力する

前回と前々回でパラシリ変換器(sin_gen)と正弦波生成 IP(para2serial) が作成できました。今回は、これらを組み合わせて、ZYBOから音を出力したいと思います。

環境

  • パソコン:Windows10 64 bit
    • Vivado 2020.2 をインストール
    • Xilinx Vitis 2020.2 をインストール
  • ボード:Zybo Z7-20
  • 出力デバイス:スピーカー

システムの構成

システムの構成を図1に示します。

図1:システムの構成
図1:システムの構成

データをスピーカーに出力する手順は以下です。

  1. 「AXI GPIO」で正弦波生成 IPに周波数値を与える
  2. 正弦波生成 IPの出力値をパラシリ変換 IPでシリアルに変換する
  3. コーデックでシリアルデータを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リポジトリの追加
図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の設定
図2:AXI GPIOの設定

Clocking Wizard は以下のように設定します。

  • Input Frequency:50 MHz
  • Output Freq:96 MHz
  • locked:チェックOFF

図3:Clocking Wizardの設定(Clocking Options)
図3:Clocking Wizardの設定(Clocking Options)

図4:Clocking Wizard の設定(Output Clocks)
図4:Clocking Wizard の設定(Output Clocks)

Binary Counter は以下のように設定します。

  • Output Width:2

図5:Binary Counterの設定
図5:Binary Counterの設定

Slice は以下のように設定します。

  • Din Width:2
  • Din From:1
  • Din Down To:1
  • Dout Width:1

図6:Sliceの設定
図6:Sliceの設定

配線は図7のように接続します。

図7:配線後のIPインテグレーション
図7:配線後のIPインテグレーション

配線ができたら、「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 もあるので好ましくありません。

図8:Worst Negative Slack の値
図8:Worst Negative Slack の値

原因は、非同期クロック間で不必要なタイミング解析を行っているからです。MMCMに入力される50MHzのクロックとMMCMから出力される96MHzのクロックは非同期ですが、今回は同期しているとしてタイミング解析がされてしまいました。

非同期クロック間でタイミング解析を行わないように制約を与えます。「Timing」タブ→「Inter-Clock Paths」を選択して、クロックのペアを右クリックしてから「Set Clock Groups...」で設定できます(図9)。

図9:「Set Clock Groups」を開く
図9:「Set Clock Groups」を開く

「Set Clock Groups」では、「Group name」を記入して、「OK」を押します。私の場合は、「Group name」を「50MHz_and_96MHz」としました(図10)。

図10:「Set Clock Groups」の設定
図10:「Set Clock Groups」の設定

設定を終えたら、「Timing Constraints」タブの「Apply」で設定を適用します(図11)。

図11:「Timing Constraints」の設定を「Apply」する
図11:「Timing Constraints」の設定を「Apply」する

制約を適用できたら、再び「Generate Bitstream」でビットストリームを作成します。

制約ファイルを保存するか聞かれますので、「Save」を押します(図12)。

図12:制約ファイルの保存のダイアログ
図12:制約ファイルの保存のダイアログ

次に、「論理合成と配置配線が古くなりますよ」という警告が出ますが、そのまま「OK」でいいです(図13)。

図13:論理合成と配置配線が古くなる警告
図13:論理合成と配置配線が古くなる警告

制約ファイルを更新するか、新しく作成するか聞かれますので、「Update」を押して更新します(図14)。

図14:制約ファイルを「Update」する
図14:制約ファイルを「Update」する

「Select an existing file」が選択されていることと制約ファイル名が正しいことを確認して、「OK」を押します(図15)。

図15:既存の制約ファイルに保存する
図15:既存の制約ファイルに保存する

あとはいつもの設定画面が出ますので入力すると、「Generate Bitsteam」が始まります。

「write_bitstream_Complete」が出たら、一応、タイミングを満たしているか確認します。「Reload」を押して、配置配線後のデザインをリロードします(図16)。

図16:配置配線後のデザインを「Reload」する
図16:配置配線後のデザインを「Reload」する

「Timing」タブからWorst Negative Slackを確認すると正の値になっていて、タイミングを満たしていることが確認できます。

図17: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");

    // 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) の設定
    • 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
図18:ミュートをOFF

上手くできると以下のような音が出ます。出力される波形とスペクトログラムは図19のようになります。

図19:出力される波形とスペクトログラム
図19:出力される波形とスペクトログラム

440Hz以外にも成分があると思いますが、おそらく原因は正弦波生成 IP (sin_gen) だと思います。sin_genについてはあとで作りなおしたいと思います。

変な音がでる

上の例だと出力される音が綺麗なのですが、これは良い例です。7,8割は上手くいかず、以下のような音が出ます(イヤホンでは聴かないほうがいいです)。出力される波形とスペクトログラムは図20のようになります。

図20:変なときの音の波形とスペクトログラム
図20:変なときの音の波形とスペクトログラム

原因はいまだわかっていないです。プログラムの実行順番を変えたりしたら治ったり、プログラムを元に戻しても変にはならなかったりして、原因がわかりません。ロジックアナライザで確認しても、シリアルデータはフォーマット通りのため、FPGAの外の問題なのかもしれません。

おわりに

音の出力が上手くいかず気持ち悪いですが、他のことをやりながら原因をつきとめようと思います。次の記事では、フィルタの作成を行いたいと思います。

お問い合わせフォーム プライバシーポリシー

© 2021 Setoti All rights reserved.