PCで生成した学習済みデータは既にlinks.txtとして出力したということで、ここからは、これをいかにFPGAで動作させるかに話は移っていきます。
まずはFPGAの開発環境を用意しましょう。本連載で使用するFPGA開発ボード「Tang Nano 9K」に搭載されているFPGAの開発元であるGOWIN Semiconductorは、IDE(統合開発環境)の「GOWIN EDA」を提供しています。GOWINのWebサイトにログインすると、ソフトウェアラインアップに「Education Edition」がありますので、それを選択してダウンロードしてください。このEducation Editionは無償で、2024年になってからリリースされました。関連ドキュメントも同梱されていますので使い方はそれらを参照してください。なお、日本語のドキュメントもあります。なお、本記事の執筆時に筆者が用いているGoWin EDAのバージョンは「GowinSynthesis V1.9.8.09 Education」です。
リスト4のneuro9k.vは、ホップフィールドネットワークとして生成した学習済みニューラルネットワークをFPGAで動作させるために、VerilogHDLというハードウェア記述言語(HDL、Hardware Description Language)で記述したものです。neuro9k.vのソースコードは以下のリポジトリから参照できます。リストに示す行番号は、執筆時にリポジトリにアップロードした時のもので、その後リポジトリのファイルが更新されるとここで示した行番号とずれる可能性がありますので、その点はご了承ください。
⇒neuro9k.vのソースコードのあるGitHubのリポジトリはこちら
1: module neuro (input rst,input clk,input [3:0]btn,output test1,test2,low,high,[7:0]col,[7:0]row); 2: reg [24:0] neuros; 3: reg signed[7:0] links[624:0]; 4: wire [7:0] matrix[7:0]; 5: reg[4:0] i=0,jj=0; 6: reg signed [7:0]sum; 7: reg [24:0]count12; 8: 9: assign test1 = i; 10: assign test2 = btn[0]; 11: assign matrix[0][4:0] = neuros[4:0]; 12: assign matrix[1][4:0] = neuros[9:5]; 13: assign matrix[2][4:0] = neuros[14:10]; 14: assign matrix[3][4:0] = neuros[19:15]; 15: assign matrix[4][4:0] = neuros[24:20]; 16: assign col = matrix[count12[15:13]]; 17: assign row = ~(1<<count12[15:13]); 18: 19: always @(posedge(clk))count12 = count12 + 1; 20: integer k,m; 21: always @(posedge(clk))begin 22: if (rst==0) begin i = 0;neuros = 25'b0111010011100100001001110;end 23: if ((btn[0]==0)&&(i==0))begin 24: for (k=0;k<25;k=k+1) begin 25: sum=0; 26: for (m=0;m<25;m=m+1) 27: sum = sum + ((neuros[m]==1)?links[(k<<4)+(k<<3)+k+m]:(-links[(k<<4)+(k<<3)+k+m])); 28: neuros[k]=(sum>0)?1:0; 29: end 30: i = 1; 31: end 32: end 33: always @(posedge(clk))if (rst==0) begin `include "links.txt" end 34: endmodule
1行目で、このモジュールの名前と入出力ポートを宣言しています。入力ポートとしてrst、clk、btnがあります。それぞれ、Tang Nano 9Kのリセットボタン、クロック、btnは4ビット長で定義されていますが、そのうちのbtn[0]のみ使用しています。これは推論を開始するためのボタンです。出力ポートですが、test1とtest2は動作時間を測定するためのテストピンです。lowとhighは出力ピンをGNDあるいはVCCとして用いるためのものです。colとrowはそれぞれ8ビット長の出力ポートで、連載第1回でも紹介したTang Nano 9KとつながるLEDドットマトリクスに接続するためのものです。
ここからは大切な役割を担うコードのみ解説します。
2行目のneuroは25ビット長のレジスタで5×5の文字データが0/1のビット列で格納します。推論させたい文字、推論結果の文字、その両方でこのレジスタが使われます。3行目のlinksは8ビット長のレジスタを625列用意します。PCで生成した学習済みデータが格納されます。6行のsumは推論時の積和の値を格納する符号付き8ビット長のレジスタです。7行のcount12は25ビット長でクロック(clk)に同期してカウントアップされます。ちなみにこのボードのクロック周波数は27MHzです。
11〜15行目は5×5の文字データをLEDドットマトリクスに表示させるための処理です。16〜17行目はLEDドットマトリクスを制御するためのコードです。詳しくは、筆者がこれまでに執筆した以下の記事を参照してください。
19行目では、clkの立ち上がりでcount12をカウントアップしています。20〜31行目までが推論を実行するコードです。リセット(rst)が押されるとneurosに推論させたい文字データが代入されます。また、btn[0]が押されると推論を実行します。これについては、本連載第2回のC言語のソースコードの解説が参考になります。
33行目はリセット(rst)が押されたとき、学習済みデータをlinksに代入します。代入するコード自体は先述したlinks.txtに記述しています。なお、このファイルはこのソースコードと同じディレクトリに置いておく必要があります。
ちょっと注意して頂きたいのが22行目です。これはリセットボタンが押された時に実行されるコードになります。その時、neurosに推論させたい文字のデータを代入するのですが、そのビット列が上位ビットと下位のビット列が反転しているのです。いわゆるエンディアンが逆になっているというわけです。
リスト5をご覧ください。(1)25桁のビット列を5×5の文字として見やすいように5行に書き直したものです。(2)はさらに見やすくするために1のところを“#”とし0のところは空白としました。(3)は(2)の左右を反転し、(4)ではさらに上下を反転しました。これで実行例で示す推論させた文字の写真と一致しますね。(5)はそれを0/1に戻したものです。
01110 | ### | ### | ### | 01110 10011 | # ## | ## # | # | 01000 10010 | # # | # # | # # | 01001 00010 | # | # | ## # | 11001 01110 | ### | ### | ### | 01110 (1) (2) (3) (4) (5)
リスト6は、1行目がリスト4の22行目にある25桁の文字データになっています。2行目は、リスト5の(5)を1行で表したものです。両者を比較すると、ビット列の上位と下位が反転していることが分かります。
0111010011100100001001110 0111001000010011100101110
もし、皆さんの方で推論させたい文字を22行目に代入する場合は、その点に注意してコーディングしてください。
Copyright © ITmedia, Inc. All Rights Reserved.