Implemented a Z80 SoC (System on Chip) on TinyFPGA BX utilizing abnoname/iceZ0mb1e.
昨日までで Yosys と arachne-pnr の使い方が少し分かったきたので、今日は何か大きな回路を実装してみようと思います。ターゲットとしては、RISC-V は今まさに流行の中にありますし、今日は金曜日ということもあるので(?)往年の Z80 を対象とすることにしました。
ネットを探したところ、abnoname (Franz Neumann) さんという方の iceZ0mb1e というプロジェクトを見つけたので、それをお借りします。このプロジェクトでは TinyFPGA BX で使用している iCE40LP8K(以下 iCE40 は冗長なので略)は対応していないのですが、特殊な IP を使っている訳ではないので、HX8K の構成でそのまま合成できそうです。なお、この SoC(?)は、UART や I2C、SPI を持っていて本格的です。ソースコードを読むと、SPI って Verilog でこうやって 実現できるんだ、と勉強にもなります。
ちなみに、iceZ0mb1e で公式対応している UP5K では SPRAM というものを 128K バイトも搭載しているので Z80 用メモリには事欠きません。一方で HX8K も LP8K も、搭載メモリは 16K バイトなのが、ちと残念なところですが、いまのところはマイコンゲームを動かす訳ではないので良しとします。(TinyFPGA BX は外部ピンが多いので、やろうと思えば外部 SRAM を繋ぐこともできそうです。)
まずは合成してみる
ファイルを少しだけ修正して(今後、公開予定。大した修正ではありませんが)、合成してみます。
まず最初に、Yosys の出力を見て、どの程度の IP が利用されているか調べてみます。(昨日調べた BLIF の知識が役立ちます。 🙂 )
$ fgrep '.gate SB_LUT' synthesis/_fpga.blif | wc 2860 20020 351262 $ fgrep '.gate SB_DFF' synthesis/_fpga.blif | wc 669 4249 68180 $ fgrep '.gate SB_RAM' synthesis/_fpga.blif | wc 32 2496 69216
つまり、LUT4 を 2860ブロック、D-FF を 669ブロック、Embedded RAM を 32ブロック使っているということですね。
続いて Placing/Routing
続いて、arachne-pnr の結果です。合成には、iMac (27-inch, Late 2012)(3.4 GHz Intel Core i7)上の VMware ゲスト OS で 30秒といったところです。
After packing: IOs 17 / 63 GBs 0 / 8 GB_IOs 0 / 8 LCs 3365 / 7680 DFF 669 CARRY 187 CARRY, DFF 0 DFF PASS 393 CARRY PASS 59 BRAMs 32 / 32 WARMBOOTs 0 / 1 PLLs 0 / 1
最終的に、logic cell 3365個に納まることを確認できました。
次に、タイミングアナリシスをしてみましょう。この Z80 SoC は何 MHz で動作するのでしょうか。
$ icetime -tmd lp8k synthesis/_fpga.txt (略) Total path delay: 54.20 ns (18.45 MHz)
ということで、とりあえず 16MHz ならば問題なさそうです。icetime については、Project iceStorm の本家サイトが詳しいです。
さて。今回は実際にデバイスをコンフィグレーションして動かすことはしませんので、ここで終わってしまっては面白くありません。(動かそうとすると、後述のファームウェアを読んだり、UART を繋いだりしないと分析できないので、それは次回(あるのか?)に回したいと思います。)
ちょっと、makefile を追いかけてみましょう。
make 編(ファームウェアのビルド)
iceZ0mb1e のサイトを見ると、最初に make firmware することになっています。そこでは何をしているのでしょうか。まず最初に驚くのが、ファームウェア(Z80 でファームウェアって言い方をするのは、私にとって初めて!)が C 言語で書かれていることです。素晴らしい。Z80 の開発も素晴らしく楽そう。 🙂
コンパイル・リンク結果は、Verilog で利用できる VMEM フォーマットと呼ばれる hex 形式に変換されます。そのファイルは、Verilog 中の $readmemh という構文に与えられ、reg 構文で定義されたメモリの初期値として利用されます。無事に ROM ができました。(なーんて偉そうに書いてますが、いま Google で調べました。ここが詳しいです。)
make 編(Yosys & arachne-pnr)
続いて make fpga です。こちらは、単純に Yosys と arachne-pnr を呼んでいるだけですね。Yosys を実行するときに -f というオプションで Verilog フロントエンドにオプションを与えてますが、こちらについては、詳説しているドキュメントを見つけられずにいます。それは、また今度。。。
yosys -q -o ./synthesis/_synth_output.v -f (略) -p (略) ./import/tv80/rtl/core/tv80_alu.v (また略) arachne-pnr -d 8k -P cm81 -p (略ばっかり) ./synthesis/_fpga.blif -o ./synthesis/_fpga.txt
make 編(シミュレーション)
続いて、Verilog のシミュレーションについて調べてみましょう。make sim を実行するとシミュレーションのデモが走ります。シミュレーションでは、Icarus Verilog(以下 iverilog)という、これもフリーの実装を使用しています。make の動きを追ってみましょう。
iverilog -s tb_iceZ0mb1e -D(略) -o ./simulation/_compiler.out ./tb/tb_iceZ0mb1e.v (略)
コマンドライン引数の -s は、トップモジュールを指定するものです。-D はマクロ定義ですね。iverilog では、直接シミュレーションを走らせるのでなく、Verilog ソースをコンパイルし、vvp(Icarus Verilog runtime engine)が実行することのできるスクリプトを出力します。その際、Verilog のファイル(おそらくテストベンチ)の中で $dumpfile という指定をすると、配線やレジスタの値の時間を示す結果をファイルとして出力します。これは、後でシミュレーション結果をツールで解析する際に利用します。
デフォルトでは、ダンプの出力形式は VCD という形式で出力されますが、iceZ0mb1e では vvp に -lxt というオプションを与えて LXT というフォーマットで出力しているようです。
vvp -v ./simulation/_compiler.out -lxt
最後に、GTKWave というツールで、出力ファイル(配線(ネットと言うの?)やレジスタの時間変化)をグラフとして出力することができます。素晴らしい! 結果を示しておきます。往年の Z80 パワーユーザーであれば、16進数を見ただけで何を実行しているか分かることでしょう。(クリックすると拡大できます。)
最初は JP 1000h ですね。次は LD SP, 9fffh ですね。後は皆さんにお任せします。 🙂
最後にフロアレイアウトを見てみる
最後に、今回合成した Z80 SoC が、iCE40 上にどのように実装されているか、レイアウトビュアーを動かしてみましょう。今回は、こちらの knielsen/ice40_viewer を使わせて頂きました。なお、このツールは iCE40 の 1K デバイスと 8K デバイスでしか動きません。私は最初 5K デバイスの実装を閲覧しようとして、なかなか表示が出てこなくて悩みました。JavaScript コンソールを見たら、ちゃんとエラーが出てました。。。
素晴らしいですねー。感激したところで、今日はここまで。