12和音PICオルゴールを作ってから、いつかFPGAで音源チップを作って、もっと豪華な音が出るものを作ってみたいと思っていました。
ある日、htsfmsというソフトウェアFM音源を知り、音のすばらしさに感動しました。携帯の着メロ用FM音源は2〜4オペですが、htsfmsは最大8オペです。これを参考に、FPGAでFM音源ジュークボックスを作ることにしました。
具体的には、スパルタン3スターターキットにSDカードをつなぎ、スターターキットの8個のスライドスイッチで選んだMIDIファイルを演奏するものです。出力は何も考えずに高音質ということでS/PDIF(fs=48kHz)とします。

FM音源部

FM音源の仕様は8オペ、フリーアルゴリズムです。フリーとは何かというと、自分のフィードバックを含め、8個のオペレータのうちどれでも、いくつでも変調入力にすることができます。また、どれでも、いくつでもキャリアになれるので、自由自在なつなぎ方が実現できます。
エンベロープはADSRを拡張し、アタック/ディケイ/リリースをそれぞれ2段階に分け、さらにノートオンからアタック開始までの時間を指定できます。
あとは、8スロットに1つLFOが付き、振幅変調と位相変調ができます。
音数は、適当なMIDIファイルを演奏してあまり不足を感じないように、32音用意します。合計256スロットになります。
fs=48kHzなので、1クロック/スロットで処理するとしても最低48k×256≒12MHzで駆動する必要があります。ただし、実際にはCPUがFM音源のレジスタを設定する時間的余裕も必要であり、もっと高い周波数で駆動しなければなりません。
一方、スターターキットのオンボードクロックは50MHzであり、S/PDIF送信部(DAIT)は128fs=6.144MHzのクロックを必要とします。

50M=2^7*5^8
6.144M=2^14*3*5^3
∴6.144M/50M=2^7*3/5^5

スパ3のDCMの可変範囲を考慮すると、

DCM1: 2^4/5^2=16/25
DCM2: 2^3*3/5^2=24/25

と設定してカスケードにして、さらに5分周すれば128fsになります。
DCM2の出力で全体を駆動しようとしたのですが、CLKFXをカスケードにするとジッタが多くて良くないらしいので、DAIT以外はDCM1の出力(32MHz)で駆動することにしました。FM音源部とDAITの間には非同期FIFOを入れます。ISE8.1iからWebpackでもCoreGenが使えるようになったので、CoreGenの非同期FIFOを使ってみました。
32MHzで1クロック/スロットで処理するために、パイプライン構成にします。ただし、最大8個の変調入力を同時に読み出すようにするためにはブロックRAMの数からXC3S1000以上が必要となるため、変調入力が2個以上のときは1個につき1クロックずつストールさせます。これでは最悪時に間に合わないのですが、実際のデータではそれほどストールすることはないので大丈夫です。

CPU

FM音源部が256スロットもあり、1スロット当たりの設定データも多い(特にエンベロープ)ので、CPUはある程度のパワーが必要です。
幸い、FM音源部はブロックRAMと乗算器はたくさん使うのですが、ロジックセルは大量に余っています。そこで、以前作ったMIPS/kコアを使います。
このコアで、SDカードからMIDIファイルを読み出し、解釈し、FM音源のレジスタを設定します。
まず、読み出すMIDIファイルを決めます。
スターターキットのスライドスイッチ8個で表す数をn(0≦n≦255)として、ディレクトリ木を深さ優先で再帰的にたどってn+1番目のMIDIファイルを先頭から読み出し、バッファリングします。
バッファリングは、開始時とMIDIのデルタタイムで待っているときだけ行います。それ以外のときにクラスタをまたいでFATを読んだりして時間がかかるのを防ぎます。
そして、バッファから1バイトずつ読み、MIDI解釈ルーチンに渡します。1バイトずつ渡せるような作りになっているので、シリアルで受けたデータを渡すようにすればMIDI音源モジュールにも改造できると思います。
なお、サポートするファイルシステムはFAT16のみ、MIDIファイルはSMF0のみです。SMF1からSMF0への変換はExSMFを使いました。

実装面では、MIPS/kコア内部では分離しているインストラクションとデータを統合し、基板上のSRAMにつないでいます。
共通のRAMにしたことで、がんばって環境を作ればC/C++で書けそうですが、コア自体のデバッグもあるのでアセンブラで書きます。
MIPSのアセンブラは昔仕事で書いていたこともあり、懐かしくていい感じです。
なお、MIPSの乗算命令は本来32ビット×32ビット→64ビットですが、ちょっとケチって32ビット×16ビット→32ビットにしています。また、除算命令はほとんど使わない割に規模が大きいので削除してあります。

DAIT

FPGA Editorを見ながらちくちく詰め込んでいくのが楽しいので、無駄にチューニングしてみました。
fs=48kHz固定ですが、わずか20スライスでデジタルオーディオの送信ができます。

音色コンバータ

音色設定ファイルのフォーマットはhtsfmsの下位互換にしました。以下の部分を簡略化しています。

Tonemapは非対応
ファズ効果はON/OFFのみで、効き具合は固定
スケーリングは1か0.5のみ
ノイズのハイパス/ローパスフィルタは無効(常に弱いハイパスがかかっている)
イニシャルレベルは無効
LFOのAtとDlyは無効

実装

FPGAは、RTLシミュレーションまでは純粋にロジックだけの世界を堪能できる楽しいアイテムです。
が、今回初めてFPGAならではのトラブルがありました。
7セグLEDに何か表示しようとしてそのようにHDLを書き換えると全く動かなくなるという現象です。
調べると、動かないときはDCMがロックせず、まともなクロックが出ていないことが分かりました。
困り果てて何気なくFPGA Editorで確認すると、動くときと動かないときでは使われるDCMが違うことが分かりました。
そこで、動くときの配置をUCFに書いて固定すると、DCMがロックしないという現象は出なくなりました。
これでしばらく凌いでいたのですが、あるとき根本的な解決法が分かりました。
今まで特に問題がなかったのでスパルタン3のコンフィグをマスターモードにしていたのですが、 それではJTAGからのコンフィグがうまくいかないことがあるそうです。

Xilinxアンサー22255

これが曲者なのは、コンフィグ後にたまに起動に失敗するという症状ばかりでなく、デザインによっては常に同じ異常動作をするところです。
この情報を元にスターターキットのM0とM2のショートプラグを外してJTAGモードにしたら配置制約を削除しても変な症状は全く出なくなりました。

Device Utilization Summary
Logic UtilizationUsedAvailableUtilizationNote(s)
Number of Slice Flip Flops 878 3,840 22%  
Number of 4 input LUTs 1,801 3,840 46%  
Logic Distribution    
Number of occupied Slices 1,352 1,920 70%  
Number of Slices containing only related logic 1,352 1,352 100%  
Number of Slices containing unrelated logic 0 1,352 0%  
Total Number 4 input LUTs 2,380 3,840 61%  
Number used as logic 1,801      
Number used as a route-thru 63      
Number used for Dual Port RAMs 320      
Number used for 32x1 RAMs 88      
Number used as Shift registers 108      
Number of bonded IOBs 104 173 60%  
IOB Flip Flops 11      
Number of Block RAMs 11 12 91%  
Number of MULT18X18s 7 12 58%  
Number of GCLKs 2 8 25%  
Number of DCMs 2 4 50%  
Number of RPM macros 1      
Total equivalent gate count for design 822,519      
Additional JTAG gate count for IOBs 4,992      



写真右の基板の青いのがSDカードです。片面基板のため、SDカードスロットを裏面に付けています。

ビルドのしかた

準備として、MIPS用のGNU binutilsと、音色コンバータがC#の実行ファイルなのでWindows以外ではMONOが必要です。
なんでC#なのかというと、単にC#を勉強しようと思っただけです。
いったんmakeしたあと、以下のファイルをISEのプロジェクトに登録してビルドします。

*.ngc
*.ucf
*.v
*.xaw
*.xco

ビルドしてFPGAにfm_top.bitファイルを流し込んだら、7セグLEDに「SErI」と表示されるので、0.hexを115200bps/8/N/1で送ります。
受信中は7セグLEDの右端のピリオドが点滅します。
受信が完了して正常にブートできると、7セグの表示がファイル番号(16進数)に変わり、演奏を開始します。
4つのプッシュボタンは、左から

スライドスイッチで指定した曲を演奏する
1つ前の曲を演奏する
現在の曲を最初から演奏する
1つ後の曲を演奏する

です。
ハード的な追加は、UCFファイルでtxの端子がデジタルオーディオ送信、cs,sclk,sdo,sdiがSDカードです。
sdoをカードのDIに、sdiをカードのDOにつなぎます。
ピン配置を変えたいときはfm_top_ucf.tmplを変更してmakeしてください。

download(fm.tar.gz)

このソース(NGCファイルを含む)は、非営利目的に限り自由に使用できます。
なお、いっさいの保証やサポートはありません。

上記ファイルには、htsfmsの音色ファイルを含んでいます。
添付を快諾してくださった石村氏に感謝致します。

(2006/12/17修正)
dait.ngc チャネルステータスの誤りを修正
fm.ngc ノイズレベルを半分に
fm_top.v, rom.s フラッシュからのブート時の安定性向上(ユーザーデータ読み出し前にROMをリセット)
(2007/02/18修正)
mips_k.ngc 割り込みと、一部MIPS IIIのインストラクションをサポート
fm_top.v, rom.s DAIT以外のクロックを50MHzに変更。7セグのスキャンをタイマー割り込みに変更
(2007/05/13修正)
cnv.exe LFOの省略値を設定
fm.ngc LFOが正しくかからない不具合を修正
kx_mips.ngc ブートコード用ブロックRAMをブートコードを初期値とするIキャッシュに変更
rom.s ピッチベンドセンシティビティが正しく設定されないことがある不具合を修正

ISE8.2.03i(webpack)で動作確認済み

演奏サンプル

実際にMIDIファイルを演奏したサンプルです。
ZIPファイルを展開すると2つのMP3ファイルになります。
S/PDIF経由で録音したものに、あとからフェードイン処理を加えています。

download(sample.zip)