はじめに
筆者はKarutaという言語処理系を開発しています。技術的な詳細や背景を知りたい方は過去の記事FPGA向け論理回路設計のためのプログラミング言語処理系 Karuta の紹介や論理回路の高位合成についてもしくは執筆中のドキュメント(要英語チェック)をお読みいただけると幸いです。
ソフトウェアでのプログラミング言語を入門する際の定番は「Hello World」ですが、FPGA等の場合はLEDを点滅させる「Lチカ(LEDチカチカ)」が定番なのでKarutaでもやってみます。
インストール
Ubuntu Linuxをお使いの方は次のコマンドでインストールすることができます。
$ sudo snap install karuta
(他の環境の方には申し訳ないですが、今の所ソースからのビルドになります)
xorshift32
xorshift32は極めてシンプルな乱数生成のアルゴリズムで、詳細はGoogle Chromeが採用した、擬似乱数生成アルゴリズム「xorshift」の数理などの記事を参考にすると理解しやすいかと思います。
Karuta言語で書いた場合はこんな感じになります。
func main() {
var y int = 1
for var i int = 0; i < 10; ++i {
y = y ^ (y << 13)
y = y ^ (y >> 17)
y = y ^ (y << 15)
print(y)
}
}
これをxorshift32.karuta
という名前で保存すると次のように実行できます。
$ karuta xorshift32.karuta --run
print: default-isynth.karuta loaded
print: 268476417
print: 1157628417
print: 1158709409
...
ここまではよく見かける普通のスクリプト言語の説明に見えますが、ハードウェア設計に移りたいと思います。次は--compile
というオプションを付けて実行します。
$ karuta xorshift32.karuta --compile
今度はkarutaコマンドが何かメッセージを出しながらカレントディレクトリにxorshift32.v
というVerilogのファイルを出力するはずです。
... 100行ちょっとのVerilogコードを略. ...
module xorshift32(clk, rst);
input clk;
input rst;
xorshift32_main xorshift32_main_inst(.clk(clk), .rst(rst));
endmodule
こんどはこいつにクロックとリセットを入れるテストベンチを付けて実行してみます。
$ iverilog tb.v xorshift32.v
$ ./a.out
268476417
1157628417
1158709409
269814307
...
どうでしょうか?良い感じですが、一つ大きな問題が残ってます。このコードは$display()
文を使って画面に乱数を表示しているので、実機のFPGA上では意味がありません。
ということで、外部に出力してみるのですが、Karutaではメソッドにアノテーションを付けることでその引数を外部ポートにつなぐことができます。こんな感じです。
@ExtIO(output = "o")
func output(v int) {
print(v)
}
func main() {
var y int = 1
for var i int = 0; i < 10; ++i {
y = y ^ (y << 13); y = y ^ (y >> 17); y = y ^ (y << 15)
output(y)
}
}
こんどはトップレベルのモジュールxorshift32はoutputのport o
を持っているので、外側の何かに接続することができます。
... 100行ちょっとのVerilogコードを略. …
module xorshift32(clk, rst, o);
input clk;
input rst;
output [31:0] o;
mod_main mod_main_inst(.clk(clk), .rst(rst), .o(o));
endmodule
コードをちょっと整理してみます。
// ファイル単位で作られるオブジェクトのメンバーyを宣言
shared y int
@ExtIO(output = "o")
func output(v int) {
print(v)
}
// 引数tを取って次の乱数を生成して返り値で返す
func update(t int) (int) {
t = t ^ (t << 13); t = t ^ (t >> 17); t = t ^ (t << 15)
return t
}
func main() {
y = 1
while true {
y = update(y)
output(y)
}
}
LED点滅(Lチカ)
最後にようやくLEDを点滅させてみます。
// このチャンネルはch.write(v)やv = ch.read()といった形でアクセスできます。
channel ch int
func update(t int) (int) {
t = t ^ (t << 13); t = t ^ (t >> 17); t = t ^ (t << 15)
return t
}
// main()は自動的にスレッドのエントリーポイントになります。
func main() {
var y int = 1
while true {
y = update(y)
ch.write(y)
}
}
@ExtIO(output = "o")
func output(y #0) {
print(y)
}
// @ThreadEntry()とアノテートされたメソッドもエントリーポイントになります。
@ThreadEntry()
func thr() {
// #0 は1bitのscalar wireになる。
var b #0 = 0
while true {
var v int = ch.read()
// 生成された乱数が10000以下ならLEDのon-offを入れ替える。
if v < 10000 {
b = ~b
output(b)
}
}
}
このコードは二つのスレッドのエントリーポイントを持っていて、片方が乱数を生成してチャンネルに書き続けます。もう一方はその乱数を元に外部に出力する値を変化させます。
出力されたVerilogには二つのスレッドに対応する二つのステートマシン(always
ブロック)とFIFO用のRAMが含まれています。論理合成をかけてお手持ちのFPGAボード等にダウンロードして、ランダムに点滅するLEDを見ることができるはずです(筆者はZYBO Z7-20とVivadoで試してます)。
最後に
Karutaの本来の目標はFPGAでのアクセラレータの設計なので、そのうち良いデモを作ってそっちの方の記事も書ければと思ってます。