FPGAで音を鳴らす ~MachXO2ブレークアウトボードで~
手始めに「FPGAで音を鳴らす」とはどういうことかやってみた。
いろいろとドタバタすることが多くて、「FPGAをいじってみる」がなかなか進んでおりませんが、それでも慣れてきたところもあって、ちょっとずつ手を出しております。
秋月電子通商さんでYAMAHA製の音源ICを買ったまではいいのですが、ArduinoやIchigoJamで何度かテストしましたが、4MHz水晶モジュールが手に入らなかったためか、うまく動かすことができませんでした。まずは確実に動くことからしようと思ったはずなのですが、初心者の悲哀を感じておりました。回り道をしてしまいましたが、それよりはやっと手になじんできたFPGA(Lattice社製MachXO2ブレークアウトボード)で試してみた、というお話です。
楽器の音を鳴らすための基礎知識をネットのブログから学びました
ここでネットで調べた音階についての話(「音楽にまつわる数学」)を基に考えてみます。ラの音(周波数は440Hz)も楽器によって音色が異なります。基本となる音の波長には、その仲間として波長の2倍音、3倍音…が存在します。それぞれの基準音、2倍音、3倍音…の音の高さを調節してやることで、それぞれの楽器に合った音色になります。例えば、バイオリンの基準音、2倍音、3倍音…のそれぞれの一番大きい時の高さは26:11:2:1:1の比率になります。基準音がラの音(周波数は440Hz)なら、基準音が上がって下がるまでに2倍音は倍の速さで上下し、3倍音は3倍の速さで上下します。プログラムとしては基準音が一巡するまでの間(上下する間の16種類×5波長分で80種類の音の大きさを準備)、基準音から5倍音までを比率に従って足し合わせた値(音の大きさ)を用意して、音色(たとえばラの音)の周波数に合わせて出力する電圧(大きさ)を切り替えていけばスピーカーから音色となって出てきます。
FPGAからは1か0かの音(矩形波)しか出てこないので、なめらかに(波形整形)しました
FPGAの出力(sound_out信号)に直接ヘッドホンをつなげても音階は聞こえます。ヘッドホン端子は秋月電子通商さんでブレッドボード用のものを購入して、便利でした。回路図と波形整形あり/なしの時の波形を示します。
結果:当たり前ですが、ちゃんと音を出すのはむつかしい
今回使ったFPGAは水晶振動子が4.16MHzと低い周波数なので、各音階で発生できた音は狙った周波数の音を出すだけの正確なものではありませんでした。ギターのチューニングメーターで音階の周波数を測ってみましたが、半音くらいずれているようでした。このFPGAボードには追加の水晶振動子として40MHzくらいのをはんだ付けするスペースがありますが、それでも足りないかもしれません。水晶振動子を変えるのとプログラムをもっと素早くするなど、時間待ちをするためのループカウントの値をもっと大きくしないと周波数を調整するだけの正確さは得られないと思いました。
今回はバイオリンの音を出してみましたが、そういわれればそれらしく聞こえるといったところです。私としてはギターの音で聞いてみたかったので、ギターの場合の比率がわかれば試してみたいところです。
<参考>
sound_FPGA1.v
`timescale 1ns / 1ps`timescale 1ns / 1ps
module sound_FPGA (
input rst,
output [7:0] led
, output [15:0] count_clk
, output [15:0] count_16x5
, output [7:0] onkai
, output [15:0] count_clk2
, output [7:0] count_sound_length
, output sound_out
);reg [23:0] count;
reg [15:0] count_clk;
reg [15:0] count_16x5;
reg [7:0] onkai;
reg [7:0] loop_table[0:24];
reg [15:0] count_clk2;
reg [7:0] sound_table[0:80];
reg [7:0] loop_count_10usec;
reg sound_out;
reg [7:0] count_sound_length;// Internal OSC setting (4.16MHz)
OSCH #( .NOM_FREQ("4.16")) IOSC
(
.STDBY(1'b0),
.OSC(clk),
.SEDSTDBY()
);// LED loop test.
always @(posedge clk or posedge rst)
begin
if (rst)
count = 0;
else
count = count+1;
end
assign led = ~count[23:16];
//<波形生成のための二つのテーブル>
// バイオリンの波形は1倍音から5倍音までが26:11:2:1:1の
// 比で表せるそうだ。(SlideShare「音楽にまつわる数学 〜「倍音」で理解する和音と音色〜」)
// https://www.slideshare.net/yuukiiwabuchi9/ss-77105134?next_slideshow=2
// それを合成した波形を作るために、1波長を16分割して5倍音まで組み
// 込むために16×5=80個の配列のテーブルを作った(sound_table)。
//
// そして、80個をつないでいくまでの時間を音階(440Hzのラから
// 880Hz のラまで)ごとに用意するために25個のテーブルを作った
// (loop_table)。
//
// ただし、ギターのチューニングメーターで音階を測ってみたが、半音くらい
// ずれているようだ。FPGA(MachXO2ブレークアウトボード)の内蔵振動子は
// 4.16MHzで、これが限界かな?と思っている。
//<80個のsound_table[]を音階に従って時間待ちをする処理ルーチン>
// 現在のcount_16x5の値を生成する
always @(posedge clk or posedge rst) begin
if (rst) begin
onkai <= 3;
count_clk <= 0;
count_16x5 <= 0;
loop_table[0] <= 236; //220Hz A
loop_table[1] <= 223;
loop_table[2] <= 211; // B
loop_table[3] <= 199; // C
loop_table[4] <= 188;
loop_table[5] <= 177; // D
loop_table[6] <= 167;
loop_table[7] <= 158; // E
loop_table[8] <= 149; // F
loop_table[9] <= 141;
loop_table[10] <= 133; // G
loop_table[11] <= 125;
loop_table[12] <= 118; //440Hz A
loop_table[13] <= 112;
loop_table[14] <= 105;
loop_table[15] <= 99;
loop_table[16] <= 94;
loop_table[17] <= 89;
loop_table[18] <= 84;
loop_table[19] <= 79;
loop_table[20] <= 74;
loop_table[21] <= 70;
loop_table[22] <= 66;
loop_table[23] <= 63;
loop_table[24] <= 59; //880Hz A
end
else if (count_clk == loop_table[onkai]) begin
if (count_16x5 > 16*5-1) begin
count_clk <= 0;
count_16x5 <= 0;
end
else begin
count_clk <= 0;
count_16x5 <= count_16x5 + 1;
end
end
else begin
count_clk <= count_clk + 1;
end
end
// <10μ秒ごとに信号波形の高さを求める処理ルーチン
// 実際にストレージオシロで観測したら、20μ秒ごとでした>
// sound_outのON時間が出力信号波形の高さとなる。
// 上下する波形の高さを10μ秒ごとにsound_table[count_16x5]から求める。always @(posedge clk or posedge rst) begin
if (rst) begin
count_clk2 <= 0;
loop_count_10usec <= 61;
sound_out <= 0;
count_sound_length <= 61;sound_table[0] <= 31;
sound_table[1] <= 36;
sound_table[2] <= 41;
sound_table[3] <= 45;
sound_table[4] <= 49;
sound_table[5] <= 52;
sound_table[6] <= 55;
sound_table[7] <= 58;
sound_table[8] <= 59;
sound_table[9] <= 60;
sound_table[10] <= 61;
sound_table[11] <= 61;
sound_table[12] <= 62;
sound_table[13] <= 61;
sound_table[14] <= 61;
sound_table[15] <= 60;
sound_table[16] <= 60;
sound_table[17] <= 59;
sound_table[18] <= 58;
sound_table[19] <= 57;
sound_table[20] <= 56;
sound_table[21] <= 54;
sound_table[22] <= 53;
sound_table[23] <= 51;
sound_table[24] <= 49;
sound_table[25] <= 47;
sound_table[26] <= 45;
sound_table[27] <= 43;
sound_table[28] <= 42;
sound_table[29] <= 40;
sound_table[30] <= 39;
sound_table[31] <= 38;
sound_table[32] <= 37;
sound_table[33] <= 36;
sound_table[34] <= 36;
sound_table[35] <= 35;
sound_table[36] <= 34;
sound_table[37] <= 33;
sound_table[38] <= 33;
sound_table[39] <= 32;
sound_table[40] <= 31;
sound_table[41] <= 30;
sound_table[42] <= 29;
sound_table[43] <= 29;
sound_table[44] <= 28;
sound_table[45] <= 27;
sound_table[46] <= 26;
sound_table[47] <= 26;
sound_table[48] <= 25;
sound_table[49] <= 24;
sound_table[50] <= 23;
sound_table[51] <= 22;
sound_table[52] <= 20;
sound_table[53] <= 19;
sound_table[54] <= 17;
sound_table[55] <= 15;
sound_table[56] <= 13;
sound_table[57] <= 11;
sound_table[58] <= 9;
sound_table[59] <= 8;
sound_table[60] <= 6;
sound_table[61] <= 5;
sound_table[62] <= 4;
sound_table[63] <= 3;
sound_table[64] <= 2;
sound_table[65] <= 1;
sound_table[66] <= 1;
sound_table[67] <= 1;
sound_table[68] <= 0;
sound_table[69] <= 1;
sound_table[70] <= 1;
sound_table[71] <= 2;
sound_table[72] <= 3;
sound_table[73] <= 4;
sound_table[74] <= 7;
sound_table[75] <= 10;
sound_table[76] <= 13;
sound_table[77] <= 17;
sound_table[78] <= 21;
sound_table[79] <= 26;
end
else if (count_clk2 >= loop_count_10usec) begin
count_clk2 <= 0;
end
else if (count_clk2 == 0) begin
sound_out <= 1; // ON
count_sound_length <= sound_table[count_16x5];
count_clk2 <= count_clk2 + 1;
end
else if (count_clk2 > count_sound_length ) begin
sound_out <= 0; // OFF
count_clk2 <= count_clk2 + 1;
end
else begin
count_clk2 <= count_clk2 + 1;
end
end
endmodule
sound_FPGA1.lpf
BLOCK RESETPATHS;
BLOCK ASYNCPATHS;
LOCATE COMP "led[7]" SITE "107" ;
LOCATE COMP "led[6]" SITE "106" ;
LOCATE COMP "led[5]" SITE "105" ;
LOCATE COMP "led[4]" SITE "104" ;
LOCATE COMP "led[3]" SITE "100" ;
LOCATE COMP "led[2]" SITE "99" ;
LOCATE COMP "led[1]" SITE "98" ;
LOCATE COMP "led[0]" SITE "97" ;
LOCATE COMP "sound_out" SITE "5" ;
LOCATE COMP "rst" SITE "1" ;IOBUF ALLPORTS IO_TYPE=LVCMOS33 ;
FORTHをZYNQで(どこまでできるやら)
タイトルにあることを始めようと思ったきっかけの話ですけど、私は社会人の最初の仕事はアセンブラでのMS-DOSパソコン(その当時の主流はラップトップパソコンで、私はデスクトップパソコンのIBM互換機グループに配属されました)のファームウェア課のディスプレイBIOS開発でした。そのころはIBM互換機が売れ出したころで、まだ漢字を扱えるIBM互換機がないから日本語版MS-DOSのIBM互換機を出そうとしていました。ディスプレイBIOSを漢字が表示できるように仕様変更を行うには、当時はアセンブラ(機械語)での開発しかありませんでした。CPUを直接機械語で動かすのは誤ったコーディングをしてもそれを発見するのが難しく、デバッグに要する時間もかなり必要で、よく「1KBの開発には1人月必要」と言われていました。新人の私が初めての経験では必死になってコーディングした後には、デスクトップパソコン本体のテスト部門から返ってきたクレームの嵐に、修正にコーディングの2、3倍ぐらいの時間をかけながら、深夜までコツコツとやってた記憶があります。それも、だれも手伝う人がいないので、周りの人に聞きまわっては一人で考える日々でした。
あれがあったから、その後転々と担当する仕事を変えながらも、30年以上パソコン屋としての経験を詰めたんでしょうけどね。
でも、今はそんな昔と状況は違い、もっと安全で効率のいいプログラミング言語が開発され、アセンブラはコーディングしたとしても、普通の人はそれを動作させることができない環境になっていると思います。無理やりねじ込めばできるんでしょうけど、そんなコードは危険で、作っても楽しくないでしょう。ただ、CPUコアの開発会社によって命令セットが全く違いますが、すべてのプログラミング言語はコンパイル/リンクされた最後はアセンブラに落とされます。アセンブラをかじっていると、案外プログラムの変な挙動も、別のプログラミング言語に移行して開発をするときも理解しやすいでしょう。初心者は無理としても、入門者には(開発するシステムのCPUコアの命令全部じゃなくて、MOVでの様々な転送モードや、割り込みや例外処理での異常処理の部分とかだけでもと思うのですが)教えてあげたほうがいいと感じています。
実はアセンブラも最初の5年間ほどやった後はいろいろ違う仕事をしていたので、雑誌やネットで記事を探して調べたりはするのですが、RUBYやCとかでまとまったコーディングはできずじまいでした。いまだに、何かあったときはコンパイルやリンクなんかしないで、直接メモリの16進数を読んではパッチを当てて書き込んでしまうコーデの修正を思い浮かべてしまいます。山のように出力したダンプリストに見つけたJMPやCALL命令に赤ペンで印をつけて次のとび先を探して、バグの追っかけっこしてました。そんなわけで、どうせ今からやるなら、安全にコア部分をいじってみたいと思って、FPGAやSoCで遊んでみようと思いました。できれば、学生時代にアメリカの天文台のマシンで本格的に生まれた、当時の限られたメモリをうまく使って、単純化した(と私は感じたけど)FORTHをハードウェア記述言語で書いてみたいと思ったわけです。まともにやっても全く歯が立たない気がしていますけど。
で、あれこれと考えるのが好きなんですが、私はとにかく時間がかかってしまうもので、試行錯誤して遊ぶ前にやった準備のあれこれを書いてみました。これから試行錯誤したことはメモにする予定です。FPGAいじったことのある人なら、あっという間に先を越されてしまうんでしょうけど。要は楽しけりゃぁ誰がやったっていいんだと思っています。自分もそうなんですが、初めてやって、ツボにはまりまくって、人からなんでか知らないけどいろいろ教えてもらったりして、気が付いてみたら楽しくなっていた、なんて最高ですよね。山登りと一緒で、やってるときは大変ですけどね。
「今までやったこと、考えたこと、これからやりたいこと」を最後にまとめました。
<今までやったこと>
1)まず秋月電子でZYBO Zynq-7010評価ボード Z7-10 開発環境ソフトライセンス付を買ってきた。
http://akizukidenshi.com/catalog/g/gM-12552/
2)秀和システム「FPGAプログラミング大全 Xilinx編 小林優[著]」を買った。
https://www.shuwasystem.co.jp/book/9784798047539.html
3)参考資料
・「標準FORTH 井上外志雄[著](絶版)」
(学生時代(8ビットパソコンの全盛期)に何回も読み返していた本)
・FORTH PROGRAMMER’S HANDBOOK 3rd edition
Edward K. Conklin, Elizabeth D. Rather [著]
(Elizabeth D. Ratherさんは最初のFORTHのプログラミングガイドを書いたことと、FORTHの会社に携わった女性で、今も活躍している(2006年現在とウィキペディアにあった)。著書が現在でもUS版アマゾンで購入できる)
・「DARK FORTH prologue Kodai[著]
(技術書典7(2019/9/22 東京池袋サンシャインシティ文化会館)で購入。
GFORTHのことが簡単にまとめられていて、読みやすいと思った。FORTH
は個人個人が好きにデザインできるから、あんまりWORDというか、関数名
に当たるものが統一してないので、これから始めるなら当分はGFORTHから
調べたほうが無難かなぁと思ってます。)
・Intel 8086およびCP/M-86版のFig FORTH 8086/88のソースリスト
http://forth.org/fig-forth/fig-forth_8086-8088_ver_10.pdf
(ただし、紙にプリンターで印刷したものをPDF化しているので、ここからアセン
ブラソースを手でパソコンに打ち込もうとしたが挫折した。文字認識ソフトを何
種類か試してみたけど、ASCIIコードなのに日本語で出力されるので、どういじ
ってもうまくいかず、自分の技量のなさに呆れた。)
<考えたこと>
1)「今現在でもFPGAでFORTH言語を書いてみたい人はいるんじゃないか?」でググ
ってみた(「FPGA FORTH」で検索)
例1)A VHDL Forth Core for FPGAs Richard E. Haskell and Darrin M. Hanna[著]
http://richardhaskell.com/files/ForthCoreMM.pdf
(今はこのペーパーを見ながらFPGAの勉強をやっています。別の機会にその時に
感じたこと、わかったことのメモをつれずれに書く予定。)
例2)J1: asmallForth CPU Core for FPGAs
https://excamera.com/files/j1.pdf
(J1という、FPGAでFORTHを実行するためのコア部分を記述したもの
で、その仕様メモ)
例2-1)
https://github.com/jamesbowman/j1
FPGA(Xilinx)をVerilog言語で記述したソースコード。J1の実動作例。
例2-2)
https://github.com/samawati/j1eforth
J1を設定したFPGAを使って、GFORTHを動かすためのJ1シミュレーター
のソースコード。J1 eFORTH。
(結局、J1-eFORTHとは、GFORTHを動かすために、C言語とVelilog言語とで役割分担しているシステムということなのかなと思いました。利点は実行速度の短縮かな?)
<これからやりたいこと>
1)本当にシンプルなワードやデータ長だけでいいから、MRUBYのバイトコードみ
たいに、FORTHのワードを16ビット長ぐらいのバイトコードに変換して、直
接FPGAかSoCに叩き込んで、Velilogで実行できないかなぁ。ただし、そのバ
イトコードがどんな動作をするかは元のFORTHのワード辞書や実行中のワードの
登録内容を見てみないとわからないだろうけど(それでいいかなぁ?)。