オリジナル4ビットCPUを用いてバイナリコードを学ぶ本連載。第5回は、新たな教材「Tang Nano 9K」に、インテル系IDEで開発したオリジナルCPU「DL166」のソースコードを移植する。
今回は、これまでインテルの「Cyclone IV」やPC上のシミュレーター「ModelSim」で動作していたVerilog-HDLのコードを、前回紹介した「Tang Nano 9K」に移植します。以降、記事内でのTang Nanoは「Tang Nano 9K」のことを指します。
Tang Nanoの開発環境(IDE)は、Tang Nano搭載のFPGAの製造元であるGOWIN Semiconductor(以下、GOWIN)から提供されています。Verilog-HDLで書かれているとはいえ、各IDEを提供しているベンダーにより多少癖があるようです。C言語のようにメジャーで歴史のある言語ではポータビリティが保証されているとともに、プログラマーはポータビリティを意識したコーディングを心がけるマナーが定着しているのかもしれません。これに対してVerilog-HDLは、今回の移植作業を通してそのあたりがまだ枯れてきってないことを実感しました。やはり圧倒的なプログラマー人口と歴史の差なのかもしれません。
インテル系の開発環境ではFPGAのIDEとシミュレーターのModelSimとの間で同じVerilog-HDLのソースコードを交換しながら開発を進めることはよくあることなのですが、一方のツールでコンパイルが通ったソースコードがもう一方のツールでエラーになるということはほぼありませんでした。しかし提供するベンダーが異なるとそう簡単ではありません。そんなわけで今回は、Verilog-HDLのソースコードの移植に関してのポータビリティに関わる知見を共有できればと思います。
今回の作業は、今まで紹介してきた「DL166」のVerilog-HDLのコードをTang Nanoに移植します。DL166とは筆者が教育用にデザインした4ビットCPUです。レジスタ構成やインストラクション(命令)セットについては連載第1回記事をご覧ください。
移植元のVerilog-HDLのコードはインテルが提供するFPGA開発用のIDE「Quartus」で開発しており、FPGAのCyclone IVで動いています。また、実機(FPGAのチップ)に持っていく前にPC上で動作を確認するシミュレーターのModelSim(Quartusと併せて提供される)でも動いており、これら2つでコードの動作を確認しています。
Tang Nanoへの移植に際しては、これらのコードをGOWIN提供のIDEによってコンフィギュレーションするのですが、ここでVerilog-HDLの互換性の問題が発生しました。ModelSimやQuartusでコンパイルできたコードそのままでは、GOWINのIDEでシンセサイズできないのです。Verilog-HDLからロジックを生成する過程をコンパイルあるいはシンセサイズといいますが、これらはツールによって呼び方はまちまちです。そこでGOWINのIDEでシンセサイズが通るようにVerilog-HDLのコードを書き換えました。
移植元となるインテル系のVerilog-HDLソースコードはGitHubに掲載しています。
このソースコードにおいて、フェッチしてきた命令セットをデコードするためにcasez(op)を使っています(リスト1)。先述のソースコードをそのままGOWINのIDEでシンセサイズすると、このあたりが具合が悪いらしくエラーが出てしまいます。リスト1は、CPUの中のデコードと実行の部分のみになりますが、インテル系のIDEとGOWINのIDEの間でVerilog-HDLの互換性が問題になる箇所はこの部分だけなのでそこだけ掲載しました。
casez(op) /* MOV */ 5'b00zzz: regs[op[2:0]]<=regs[sss]; /* ADD */ 5'b01000: begin regs[0]<=regs[0]+regs[sss]; c_flag <= (regs[0]+regs[sss] > 15)?1:0;end /* OR */ 5'b01001: regs[0]<=regs[0]|regs[sss]; /* AND */ 5'b01010: regs[0]<=regs[0]®s[sss]; /* XOR */ 5'b01011: regs[0]<=regs[0]^regs[sss]; /* INC */ 5'b01100: begin regs[sss]<=regs[sss]+1; c_flag <= (regs[sss]+1 > 15)?1:0;end /* NOT */ 5'b01101: regs[sss]<=!regs[sss]; /*RROTATE*/ 5'b01110: regs[sss]=regs[sss]>>1| (regs[sss]<<3 & 4'b1000); /*LROTATE*/ 5'b01111: regs[sss]=regs[sss]<<1| (regs[sss]>>3 & 4'b0001); /* JNC */ 5'b1000z: begin regs[7]<= (c_flag)?regs[7]+1:{op[0],sss};c_flag=0;end /* JMP */ 5'b1001z: regs[7]<= {op[0],sss}; /* MVI */ 5'b1010z: regs[0]<= {op[0],sss}; endcase /* PC++ */ if(op[4:1]!=4'b1000 && op[4:1]!=4'b1001) regs[7]=regs[7]+1;
GOWINでシンセサイズできるように手を加えたのがリスト2です。今回の移植では命令セットでのコードの処理でcase文をやめてif文に置き換えました。if文は条件に適合した場合にそれに続く文を実行し、そのあとのif文は実行しないでブロックを抜けるというのが実行効率を考えれば良いのでしょうが、そのあたりは追い追い改良していきます。
3項演算子を使っているところも、あまりGOWINのシンセサイザーには相性が良くないようでこの箇所もif文で置き換えました。GOWINのIDEでは「casezはだめでif文なら大丈夫」という理由を追及するのがまっとうな手順かと思いますが、ここはまず先を急ぐことにします。今後そのあたりが明らかになれば連載の中で共有したいと思います。
/*MOV*/ if (0==(dout&192)) regs[(dout&56)>>3]=regs[dout&7]; /*ADD*/ if (64==(dout&248)) begin if (regs[0]+regs[dout&7]>15) c_flag=1;regs[0]=regs[dout&7]+regs[0];end /*OR*/ if (72==(dout&248)) regs[0]=regs[0]|regs[dout&7]; /*AND*/ if (80==(dout&248)) regs[0]=regs[0]®s[dout&7]; /*XOR*/ if (88==(dout&248)) regs[0]=regs[0]^regs[dout&7]; /*NOT*/ if (104==(dout&248)) regs[dout&7]=~regs[dout&7]; /*RLOTATE*/ if (112==(dout&248)) regs[dout&7]=((regs[dout&7])>>1)|((regs[dout&7]&1) << 3); /*LLOTATE*/ if (120==(dout&248)) regs[dout&7]=((regs[dout&7])<<1)|((regs[dout&7]&8) >> 3); /*INC*/ if (96==(dout&248)) begin if (regs[dout&7]+1>15) c_flag=1;regs[dout&7]=regs[dout&7]+1;end /*JMP*/ if (144==(dout&240)) regs[7]=dout&15; /*MVI*/ if (160==(dout&240)) regs[0]=dout&15; /*JNC*/ if (128==(dout&240)) begin regs[7]=((c_flag)?regs[7]+1:dout&15);c_flag=0;end /*PC++*/ if ((144!=(dout&240)&&(128!=(dout&240)))) regs[7]=regs[7]+1;
リスト2のようにソースコードに手を入れることで、GOWINのIDEでシンセサイズできるようになりました。数字の表現を10進数にしていますが、これは次回以降でC言語に変換するためです。この狙いの詳細については連載の中で説明しますが、Verilog-HDLとC言語とでは16進数や2進数の表現が異なります。将来的にマイコンでDL166のエミュレーターを実装する際の配慮です。
Copyright © ITmedia, Inc. All Rights Reserved.