0. はじめに
筆者はギタリストです。加齢とともに年々ヘタクソになっていくせいもあり、最近は弾くことよりも機材をいじっていることのほうが多くなりました。ジャンクの真空管ギターアンプを格安で買ってきて修理して使うことがあるのですが、修理するときにいちいちギターを繋いで、ジャラ~ンって鳴らすのって結構面倒です。周波数が安定していないので、オシロスコープで表示しても、どんな波形になっているのかよくわからないし、数秒で減衰するので何度も弾かないといけなかったりして。
そこで安価なFPGAボード(確か数千円だったような)SoCKitで信号発生器作成してみました。FPGAで何かを作るときの参考になれば幸いです。
こちらが完成後、ギターアンプのレストア作業に使っているところ。PCBすら無かった時代のものと先端的なデバイスのアンバランスな組み合わせがいいですね~
それではどうやって作成したか解説してきましょう。
設計手順
ちなみにツールはMATLAB/SimulinkとQuartus/Qsysを使いました。
大まかな手順としては以下の通りです。
- 既存のI2CやI2SのインターフェイスSimulinkモデルからHDLコード生成/IPコア生成
- Qsysで生成したIPコアと統合してシステムの設計
- QsysのシステムをMATLABに登録
- Simulinkで信号発生器のアルゴリズムを作成
- シミュレーションでアルゴリズム、UIの動作確認
- 信号発生器のHDLコード/IPコアを生成して全て統合(Quartusでコンパイル~ダウンロード)
- MATLABでFPGAボードコントロール用のGUIを作成
- 実機テスト
GitHubに必要なファイルは置いてあるので同じようなものを作りたい人は好きに流用して下さい。
信号発生器の仕様
- サンプリング周波数は48kHz
- 発生する信号の周波数はとりあえず20~9kHzぐらいが出ればいい
- 波形はSin、 鋸波、矩形波がそれぞれ独立してOn/Offできる
- ボードの電源を入れるととりあえず使えて、SWで音量、周波数、波形の設定が出来る
- Push Sw0,1で音量Up/Down
- Push Sw2,3で周波数を段階的にUp/Down (20, 50, 100, 200,...8000, 8500, 9000Hz)
- PC(MATLAB)と接続するともうちょっと細かい調整ができる
- 周波数は任意の値が設定できる
- Volume調整
- 波形のOn/Off
1. 既存のI2CやI2SのインターフェイスSimulinkモデルからHDLコード生成/IPコア生成
信号発生器の回路を作成する前にFPGAのインターフェイス回路など、周辺部分を作成して、MATLABで使えるように登録する必要があります。
I2CとI2SのIPの作成
SoCKitボードにはSM2603というAudio CODECが実装されています。FPGAからこのCODECを介してオーディオ信号を入出力するために、インターフェイスを作成します。
HDL Coderのヘルプドキュメントにある例を参考にして、I2SとI2CのIPを生成します。設定は以下のとおり。
あと、注意点として、これらのIPコアを生成するときにはAXIスレーブIPの幅を13に設定しておく必要があります。
2. Qsysで生成したIPコアと統合してシステムの設計
Intel Quartusを起動、New Project Wizardを起動してボードを指定して、空のプロジェクトを作成します。
ToolメニューからQsysを開いて以下のIPを追加します。
- Arria V/Cyclone V Hard Processing System: 電源を入れたらすぐ使えるよう、電源起動時CPUからFPGAをプログラムするために使います。
- Altera PLL: ユーザロジックにクロックを与えるためのPLLです。
- Audio Clock for DE-series Board: オーディオCODEC(ADC/DAC)にクロック入力するためのPLLのIPです。
以下のポートはExportをダブルクリックして外部ピン接続の設定にしておきます。
audio_pll_0/audio_clk
I2C_SSM2603/I2C_CLK, I2C_DATA, MUTEN
I2S_SSM2603/Bit_clock, RECLK, Serial_data_in, PBCLK, Serial_data_out
Qsysで作成したデザインをaudioSystemSockit.qsysとして保存します。SaveするとAXI4 Streamが接続されてないとエラーが出ますが、後でこれはHDL Coderが生成したIPと接続するので、エラーを気にせず進めます。
先ほどExport設定したポートのピンアサインメントを行い、tclファイルに保存します。
tclファイルの内容(クリックすると展開)
# Pin Assignments
# PLL
set_location_assignment PIN_Y26 -to pll_0_refclk
set_instance_assignment -name IO_STANDARD "2.5 V" -to pll_0_refclk
#Audio Codec
set_location_assignment PIN_AC9 -to audio_pll_0_audio_clk_clk
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to audio_pll_0_audio_clk_clk
set_location_assignment PIN_AH30 -to i2c_ssm26_ip_0_i2c_clk_pin
set_instance_assignment -name IO_STANDARD "2.5 V" -to i2c_ssm2603_0_i2c_clk_pin
set_location_assignment PIN_AF30 -to i2c_ssm26_ip_0_i2c_data_pin
set_instance_assignment -name IO_STANDARD "2.5 V" -to i2c_ssm2603_0_i2c_data_pin
set_location_assignment PIN_AD26 -to i2c_ssm26_ip_0_muten_pin
set_instance_assignment -name IO_STANDARD "2.5 V" -to i2c_ssm2603_0_muten_pin
set_location_assignment PIN_AC27 -to i2s_ssm2603_0_serial_data_in_pin
set_instance_assignment -name IO_STANDARD "2.5 V" -to i2s_ssm2603_0_serial_data_in_pin
set_location_assignment PIN_AG3 -to i2s_ssm2603_0_serial_data_out_pin
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to i2s_ssm2603_0_serial_data_out_pin
set_location_assignment PIN_AE7 -to i2s_ssm2603_0_bit_clock_pin
set_instance_assignment -name IO_STANDARD "2.5 V" -to i2s_ssm2603_0_bit_clock_pin
set_location_assignment PIN_AG30 -to i2s_ssm2603_0_reclk_pin
set_instance_assignment -name IO_STANDARD "2.5 V" -to i2s_ssm2603_0_reclk_pin
set_location_assignment PIN_AH4 -to i2s_ssm2603_0_pbclk_pin
set_instance_assignment -name IO_STANDARD "2.5 V" -to i2s_ssm2603_0_pbclk_pin
###
export_assignments
3. QsysのシステムをMATLABに登録
audioSystemSockit.qsysとtcl、FPGAボードをMATLABに登録するために、Mファイルを作成して最初に+で始まるフォルダに保存します。ちなみに+で始まるフォルダはパッケージフォルダと言って、パスを設定しなくても上位ディレクトリからフォルダ内部が認識されます。
それぞれのファイルは以下の目的のためにあります。(クリックすると展開されてコードが表示されます。)
hdlcoder_board_customization.m: カスタムボードをMATLABに登録するファイル
function r = hdlcoder_board_customization
r = { ...
'myArrowSoCKit.plugin_board', ...
};
end
plugin_board.m: カスタムボードの定義ファイル(ピン設定やデバイス型番など)
function hB = plugin_board()
% Board definition
% Construct board object
hB = hdlcoder.Board;
hB.BoardName = 'Arrow SoCKit for Audio';
% FPGA device information
hB.FPGAVendor = 'Altera';
hB.FPGAFamily = 'Cyclone V';
hB.FPGADevice = '5CSXFC6D6F31C6';
hB.FPGAPackage = '';
hB.FPGASpeed = '';
% Tool information
hB.SupportedTool = {'Altera QUARTUS II'};
% FPGA JTAG chain position
hB.JTAGChainPosition = 1;
%% Add interfaces
% Standard "External Port" interface
hB.addExternalPortInterface( ...
'IOPadConstraint', {'IO_STANDARD "2.5V"'});
% Custom board external I/O interface
hB.addExternalIOInterface( ...
'InterfaceID', 'LEDs General Purpose', ...
'InterfaceType', 'OUT', ...
'PortName', 'GPLED', ...
'PortWidth', 4, ...
'FPGAPin', {'AF10', 'AD10', 'AE11', 'AD7'}, ...
'IOPadConstraint', {'IO_STANDARD "3.3-V LVTTL"'});
hB.addExternalIOInterface( ...
'InterfaceID', 'Push Buttons', ...
'InterfaceType', 'IN', ...
'PortName', 'GPKEY', ...
'PortWidth', 4, ...
'FPGAPin', {'AE9', 'AE12', 'AD9', 'AD11'}, ...
'IOPadConstraint', {'IO_STANDARD "3.3-V LVTTL"'});
hB.addExternalIOInterface( ...
'InterfaceID', 'Switches', ...
'InterfaceType', 'IN', ...
'PortName', 'GPSW', ...
'PortWidth', 4, ...
'FPGAPin', {'W25', 'V25', 'AC28', 'AC29'}, ...
'IOPadConstraint', {'IO_STANDARD "2.5V"'});
hdlcoder_ref_design_customization.m: リファレンスデザイン(Qsysで作成したデザイン)を登録するファイル
function [rd, boardName] = hdlcoder_ref_design_customization
% Reference design plugin registration file
rd = {'myArrowSoCKit.qsys_base_160.plugin_rd'...
};
boardName = 'Arrow SoCKit for Audio';
end
plugin_rd.m: リファレンスデザインの定義ファイル
function hRD = plugin_rd()
% Reference design definition
% Construct reference design object
hRD = hdlcoder.ReferenceDesign('SynthesisTool', 'Altera QUARTUS II');
hRD.ReferenceDesignName = 'Audio System';
hRD.BoardName = 'Arrow SoCKit for Audio';
% Tool information
hRD.SupportedToolVersion = {'16.0'};
%% Add custom design files
% add custom Qsys design
hRD.addCustomQsysDesign( ...
'CustomQsysPrjFile', 'audioSystemSockit.qsys');
% Add constraint files
hRD.CustomConstraints = {'audioSystemSockit.tcl'};
hRD.addIPRepository(...
'IPListFunction', 'myArrowSoCKit.hdlcoder_audio_iplist',...
'NotExistMessage', 'IP repository not found');
%% Add interfaces
% add clock interface
hRD.addClockInterface( ...
'ClockConnection', 'pll_0.outclk0', ...
'ResetConnection', 'hps_0.h2f_reset',...
'DefaultFrequencyMHz', 50,...
'MinFrequencyMHz', 5,...
'MaxFrequencyMHz', 500,...
'ClockModuleInstance', 'pll_0',...
'ClockNumber', 0);
% add AXI4 slave interfaces
hRD.addAXI4SlaveInterface( ...
'InterfaceConnection', 'hps_0.h2f_axi_master', ...
'BaseAddress', '0x0000');
hRD.addAXI4StreamInterface( ...
'MasterChannelEnable', true, ...
'SlaveChannelEnable', true, ...
'MasterChannelConnection', 'I2S_SSM2603_0.AXI4_Stream_Slave', ...
'SlaveChannelConnection', 'I2S_SSM2603_0.AXI4_Stream_Master', ...
'MasterChannelDataWidth', 48, ...
'SlaveChannelDataWidth', 48 ...
);
hRD.DeviceTreeName = 'devicetree_axilite_iio.dtb';
hdlcoder_audio_iplist.m: 事前に作成したI2CやI2Sを登録するためのIPリスト
function [ ipList ] = hdlcoder_audio_iplist( )
% IP in the repository folder.
ipList = {'I2C_SSM26_ip_v1_0','I2S_SSM2603_v1_0'};
4. Simulinkで信号発生器のアルゴリズムを作成
信号発生器の回路はSimulinkで作ります。
テストベンチ部分
まず側(テストベンチ)の部分ですが、FPGAボードと同じように、Push SWやSlide SWで操作できるようにボタン類を付けました。
FPGAデザイントップ階層
FPGA実装する最上位階層はルーティングしたり、SWのインターフェイス回路など。オーディオ入力信号は信号発生器の信号とMixして、音量調整を通過して出力されるようにしてあります。
SIN波生成部分
整数をインクリメントして、鋸波を作成して、それをSIN関数に入力しています。鋸波、矩形波も出力できるようにしてあります。
5. シミュレーションでアルゴリズム、UIの動作確認
シミュレーションを行って、ボタン類の操作と出力波形の確認を行います。こういったHMIもSimulinkでシミュレーションして動作確認できました。
6. 信号発生器のHDLコード/IPコアを生成して全て統合(Quartusでコンパイル~ダウンロード)
シミュレーションで機能検証できたら、HDLコード(IPコア)を生成します。
ワークフローアドバイザーを起動したら、事前に登録したボードを選択します。
リファレンス設計のパラメータでInsert JTAG MATLAB as AXI MasterをOnに設定します。この設定をすることで、MATLABからJTAG経由でコントロールできるようになります。
インターフェイスは次のように設定します。インターフェイスAXI4に設定されているポートがPC(MATLAB)からコントロールできるレジスタになります。
MATLAB as AXI Master (AXI Manager) を追加挿入すると、ID幅をデフォルト12から13に変更しておく必要があります。
あとは上から全部実行すると、IPコア生成後、Quartusのプロジェクト生成、コンパイル、デバイスのプログラムが行なわれます。
7. MATLABでFPGAボードコントロール用のGUIを作成
FPGAボードに付いているUIはPush Sw, Slide Swがそれぞれいくつかしか付いておらず貧弱で、操作がしにくいため、いざというときにPCから細かい調整できるようにGUIを作成しました。
HDLコード生成時に、MATLAB AXI Master (AXI Manager) IPを埋め込んでいるので、MATLABから以下のようなコマンドを実行するとFPGAのレジスタ値を読み書きすることができます。
FPGAボードにJTAG接続
axiMaster = aximaster('Intel', 'interface', 'JTAG');
レジスタRead
readmemory(axiMaster, '0x108', 1)
レジスタWrite
writememory(axiMaster, '0x108', 123)
GUIの操作でレジスタ値をWriteして信号のOn/Off, ボリュームと周波数の調整ができるようにしました。
8. 実機テスト
FPGAボードから出力される信号をオシロスコープで確認しました。
おわり
参考文献
[Quartus Prime ガイド Qsys システム統合ツールの使い方] (https://www.macnica.co.jp/business/semiconductor/articles/pdf/ELS1425_Q1600_10__1.pdf)
MATLAB Example [Authoring a Reference Design for Audio System on Intel board]
(https://jp.mathworks.com/help/releases/R2020b/hdlcoder/ug/authoring-a-reference-design-for-audio-system-on-an-intel-board.html)