Swapforth on Gowin FPGA
ForthFPGA
2022-8-10 0:00 JST

Gowinの FPGA を搭載した SiPEED のTang Nano 9Kに Swapforth を載せてみた。

もともと Swapforth は Lattice 上で動いてたと思う。iCEStick で動いていたのが最初かな。さきに Retro Forth が Xilinx の FPGA で動いちゃったんでSwapforth の移植は敬遠していたのですが footprint 的に Swapforth のほうがオトクそうなので(比べたわけじゃない)移植してみました。

移植のポイントは 2つ。

Gowin UART IP Core を使う

Swapforth は 16bit の IO 空間を持っていて、その一部にUART のステータスと TX/RX が張り付いている(下記コード参照)。

top.v
// Input の記述
assign io_din_w =
    (io_addr_r[12] ? {8'd0, uart0_data_w} : 16'd0) |
    (io_addr_r[13] ? {13'd0, sw1_i, uart0_valid_w, !uart0_busy_w} : 16'd0);

// Output の記述
assign tx_req_w = io_wr_r & io_addr_r[12];

swapforth にあるもともとの uart.v はなぜか read がうまくいかなかったので(たぶんクロック早すぎ)、Gowin の UART IP Core に wrapper を作って使うようにした。ここが一番難しかった。

IP Core つくるより、たぶん uart.v をデバッグして動かしたほうがず〜〜〜〜〜と楽だったに違いない。

Gowin の Memory IP Core を使う

これは便利だった。とくにメモリに初期値を入れることができるのが便利だった。最初 2K x 16bit (= 4KB) で、後に 8KB で作り直した。

途中、PSRAM が使えないか検討してた時期があり、PSRAM もちょっと試してみた。バースト転送しかサポートしてないのとレイテンシーがめっちゃあるので採用を見送った。VRAM として使うんだろうね。

4KB 版

Swapforth の開発の軸は Zynq 用の j1b に移っているみたいで、j1a はあまりメンテナンスされていない。UART と Memory を実装した段階で、実はちゃんと動いていたのだが、見逃していた。

Swapforth の ROM は gforth で作る。cross.fs と basewords.fs と nuc.fs。cross.fs は gforth でメモリ内に Swapforth 用のコードを展開するためのツール。それをつかって、basewords.fs でアセンブラと Forth の基本Word の対応表を作る。その後、nuc.fs で、Forth として動く、ある程度の枠組みを作る。nuc は nucleus(核) の略。Retro でも似たようなことやってた。

basewords.fs と nuc.fs を ROM 化した時点で最低限の Forth が動く。nuc.hex というファイルができるので Gowin の IDE で取り込んでやればForth が動き出す。

ただし、この時点で 1 2 + .とやっても結果は出ない。: leds 4 io! ;は登録できて、1 ledsはうまく動いた(もちろん LED を配線した)。その事に後で気がついた。最初は ok しか返ってこないから、うまく動いていないものと思ってしまった。

その上、いっしょについてくる Python の shell を起動すると、のっけからエラーになって、追加 Word を登録できない。

Tethered Mode

仕方がないので nuc.fs を読む。よくみると Tethered Mode みたいなのがある。Python 側は最初 Forth がこのモードになっていると思って、ソースコードを送りつける。しかし、どういうわけか Tethered Mode になっていない。

参考までに書くとこの Tethered Mode になると、Forth は応答の末尾に0x1E (Record Separator!!) をつける。このマークを見て Python は先を続けるようになっていた。

仕方がないので、最初から Tethered Mode にして、最後に Tethered Mode から抜けるようにした。うまく動き始めたのもつかの間、あれ?途中でエラー。メモリが足りなくなったようだ。ANS Forth を動かすには 8KB 必要らしい。

8KB 版

結局、4KB 版は超簡単 Forth。使おうと思えば使えるが、メモリに余裕があるなら8KB にして ANS Forth を使いましょう。Python の shell も使えるしね。

メモリを 8KB にしたら簡単に使えるか?というとそうでもなく、j1a はメモリの read にはあるメモリ空間に jump することでそのメモリ領域を読むというトリッキーなことをやっているので、ソースの修正が必要になった。

j1a はメンテナンスされていない印象。

苦労したけど、まぁ動くようになったからよしとするか。

iverilog 版

実は実機で混沌としたので、iverilog 版を作っている。どういうふうにメモリアクセスしているかの確認のため。これをつかって、4KB 版がちゃんと動いていることに気がついた。

FPGA としてのフットプリント

さて、8KB のメモリが必要となることはわかったけど、FPGA 的なフットプリントはどうでしょう?j1a の必要レジスタは 1112。LUT は 1299。だいたい Tang Nano 9K の 17% 程度を使用。BSRAM は 8KB で 15%。

必要レジスタの大半は stack なのでこれを BSRAM とかにするともう少し小さくなるかも。まぁでも、これでも十分小さいと思うのでこのまま使うんでしょうね。

RISC-V より Forth だよ!

みんな大好き RISC-V。でも古の Forth を覚えてしまえば、そもそも C コンパイラとかいらないじゃん。しかも、あるアドレスを読み書きしようと思ったら RISC-V だとモニタプログラム作らないといけないでしょ。そのモニタは便利?変数とか使える?ちゃんとプログラムかける?

むかし Nios II でモニタ書いたときは lua を入れたね。lua は簡単にリンクできるから一つの選択肢だね。

多くの人が、arcv, argv 形式のモニタ書いて、変数やプログラムが欲しくなったところで破綻するよね。

argc, argv はバッド・ノウハウで、最初は「お〜便利便利」というものができるんだけど、そのうち拡張しすぎて混乱して「どうしよう」となる。その次は本格的に構文解析して、、、となりそうだから挫折するんだよね。

lua の前には vx-scheme を使った。これも簡単にリンクできるし、拡張性が高いから、lisp が嫌いじゃなかったら一つの選択肢。ハードの人が使えないことが多いのが難点。vx-scheme はコンパイラもついていて優秀だった。

フットプリントを考えたら Forth はかなり有力な候補。とくに FPGA なら Forth 一択でしょ!!直に IO 触れるし、拡張性も高い。swapforth に fat.fs なんてのもあったから、うまくやればファイルシステムも使えるに違いない。

リンク集