Google XLSを使ってみた

Pocket

Googleがオープンソースで公開した高位合成処理系XLSをためしてみました。サンプルは https://github.com/google/xls/tree/main/xls/examples にあるのですが、Goっぽい感じでハードウェアが設計できる処理系のようです。XLS Tools Quick Startがあるのですが、何ができるのかよくわからなかったので試してみました。

手元の Ubuntu 20.04で動作確認をしました。ちなみに Ubuntu 20.04でもVivadoは普通につかえています。

ビルド

リポジトリのREADME.md通りにビルドします。まずはInstalling Bazel on Ubuntuに従ってBazelのインストール。

$ sudo apt install curl gnupg
$ curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
$ sudo mv bazel.gpg /etc/apt/trusted.gpg.d/
$ echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
$ sudo apt update && sudo apt install bazel

Bazelは3.6.0でした。

$ bazel --version ~/src/xls
bazel 3.6.0

必要なパッケージをaptで追加して、

sudo apt install python3-distutils python3-dev libtinfo5

pythonと実行したらpython3が呼ばれるように工夫して

$ mkdir -p $HOME/opt/bin/
$ ln -s $(which python3) $HOME/opt/bin/python
$ export PATH=$HOME/opt/bin:$PATH

bazeでビルド。Core i9-9900T@2.1GHzのマシンで45分くらいかかりました。

$ git clone https://github.com/google/xls.git
$ cd xls
$ bazel test -c opt ...

ビルドが終わるとbazel-bin(シンボリックリンク)の下にあれこれ成果物が生成されます。

クイックスタートに従って使ってみる

サンプル

ビルドが終わったらクイックスタートに従って使ってみます。サンプルはこんな感じ。

fn add(x: u32, y: u32) -> u32 {
  x + y + u32:0  // Something to optimize.
}

test add {
  assert_eq(add(u32:2, u32:3), u32:5)
}

これを、(ガイドとは違って)./tmp/simpl_add.xに保存します。

インタプリタ実行

$ ./bazel-bin/xls/dslx/interpreter/interpreter_main ./tmp/simple_add.x

で、インタプリタで実行できるようです。結果は次のよう。

[ RUN UNITTEST ] add
[ OK ] add

ここで、assert_eq の第二引数(予期する答え?)を 6 にしてみると

$ ./bazel-bin/xls/dslx/interpreter/interpreter_main ./tmp/simple_add.x
[ RUN UNITTEST     ] add
./tmp/simple_add.x:4-8
  0004: 
  0005: test add {
* 0006:   assert_eq(add(u32:2, u32:3), u32:6)
        ~~~~~~~~~~~^------------------------^ The program being interpreted failed! 
  lhs: 5
  rhs:  6
  were not equal
  0007: }
  0008: 
[           FAILED ] add FailureError

と、間違ってることを教えてくれました。

IRへの変換と最適化

クイックスタートガイドによるとインタプリタで実行したあとはIRを作るようです。

$ ./bazel-bin/xls/dslx/ir_converter_main ./tmp/simple_add.x > ./tmp/simple_add.ir

実行するとIRがファイルに書き出されました。細かいところはおいておいて、雰囲気はわかる、感じです。

package simple_add

fn __simple_add__add(x: bits[32], y: bits[32]) -> bits[32] {
  add.3: bits[32] = add(x, y, id=3, pos=0,1,4)
  literal.4: bits[32] = literal(value=0, id=4, pos=0,1,14)
  ret add.5: bits[32] = add(add.3, literal.4, id=5, pos=0,1,8)
}

IRを作って最適化するよう。コマンドは次の通り。

$ ./bazel-bin/xls/tools/opt_main ./tmp/simple_add.ir > ./tmp/simple_add.opt.ir

最適化の結果でてきたのは

package simple_add

fn __simple_add__add(x: bits[32], y: bits[32]) -> bits[32] {
  ret add.3: bits[32] = add(x, y, id=3, pos=0,1,4)
}

こんな感じのコード。元のIRが、「足し算して、その結果を返す」みたいな感じだったのに対して、最適化のあとは、「足し算の結果を返す」みたいな感じですね。

Verilogファイル生成

IRからVerilogファイルを生成できます。

$ ./bazel-bin/xls/tools/codegen_main --pipeline_stages=1 --delay_model=unit ./tmp/simple_add.opt.ir > ./tmp/simple_add.v

生成されたVerilogは結構シンプルでいいですね。

module __simple_add__add(
  input wire clk,
  input wire [31:0] x,
  input wire [31:0] y,
  output wire [31:0] out
);
  // ===== Pipe stage 0:

  // Registers for pipe stage 0:
  reg [31:0] p0_x;
  reg [31:0] p0_y;
  always_ff @ (posedge clk) begin
    p0_x <= x;
    p0_y <= y;
  end

  // ===== Pipe stage 1:
  wire [31:0] p1_add_3_comb;
  assign p1_add_3_comb = p0_x + p0_y;

  // Registers for pipe stage 1:
  reg [31:0] p1_add_3;
  always_ff @ (posedge clk) begin
    p1_add_3 <= p1_add_3_comb;
  end
  assign out = p1_add_3;
endmodule

ちなみに、最適化前のIRからVerilogコードを生成することもできます。こんな感じでした。

module __simple_add__add(
  input wire clk,
  input wire [31:0] x,
  input wire [31:0] y,
  output wire [31:0] out
);
  // ===== Pipe stage 0:

  // Registers for pipe stage 0:
  reg [31:0] p0_x;
  reg [31:0] p0_y;
  always_ff @ (posedge clk) begin
    p0_x <= x;
    p0_y <= y;
  end

  // ===== Pipe stage 1:
  wire [31:0] p1_add_3_comb;
  wire [31:0] p1_add_5_comb;
  assign p1_add_3_comb = p0_x + p0_y;
  assign p1_add_5_comb = p1_add_3_comb + 32'h0000_0000;

  // Registers for pipe stage 1:
  reg [31:0] p1_add_5;
  always_ff @ (posedge clk) begin
    p1_add_5 <= p1_add_5_comb;
  end
  assign out = p1_add_5;
endmodule

比べてみると、

  assign p1_add_3_comb = p0_x + p0_y;
  assign p1_add_5_comb = p1_add_3_comb + 32'h0000_0000;

のあたりは最適化前、って感じに見えますね。(このくらいならその後の合成ツールにまかせてもいい気もしつつ)

IRの可視化

IRの可視化ツールも同梱されていて、次のようにして起動します。ローカルのWebサーバーが立ち上がりブラウザで結果を確認できます。

$ ./bazel-bin/xls/visualization/ir_viz/app --delay_model=unit --ir_path=./tmp/simple_add.ir

結果はこんな感じでした。

ノードを選択するとハイライトされたりします。HTMLを見ると、グラフの描画には、dagreを使ってるのかな?

ちなみに、最適化後のIRを可視化した結果は下のような感じでした。

まとめ

というわけで、とりあえず Google の XLS を使ってみました。他のサンプルも見ながら、どんな最適化を実装してるのか、なんかを深堀りしてみようかな、とか思ってます。