FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://sites.google.com/site/fpgadevelopindex/

A short User Guide to Chisel勉強中(2)

f:id:msyksphinz:20170806234951p:plain

関数の定義

デザインの再利用のために関数を定義することができる。

def clb(a: UInt, b: UInt, c: UInt, d: UInt): UInt = 
  (a & b) | (~c & d)

a, b, c, dの引数を取って、論理演算を実行した結果を返す。

BundleとVec

BundleVecによって、Chiselのデータ型を拡張することができる。 BundleはC言語のstructのように、複数の名前フィールドを一つの型に集約することができる。これはBundleのサブクラスとして定義する。

class MyFloat extends Bundle {
  val sign        = Bool()
  val exponent    = UInt(8.W)
  val significand = UInt(23.W)
}

val x  = Wire(new MyFloat)
val xs = x.sign

現状の実装では、Bundleのリテラルを作成することができないので、定数を挿入するためには、Bundle型のWireを作成し値を挿入する必要がある。

// Floating point constant.
val floatConst = Wire(new MyFloat)
floatConst.sign := true.B
floatConst.exponent := 10.U
floatConst.significand := 128.U

Vecはインデックス指定可能なベクタエレメントを定義する。

// 23ビットの符号付整数の5要素ベクタ
val myVec = Wire(Vec(5, SInt(23.W)))

// ベクタの1エレメントを接続する。 
val reg3 = myVec(3)

基本クラス(SInt, UInt, Bool)と複合クラス (Bundles, Vec)を含むすべての型は、基本クラスDataから派生しているクラスである。

BundleVecはネストより複雑なデータ型を作ることができる。

class BigBundle extends Bundle {
 // Vector of 5 23-bit signed integers.
 val myVec = Vec(5, SInt(23.W))
 val flag  = Bool()
 // Previously defined bundle.
 val f     = new MyFloat
}

ポート

ポートはハードウェアコンポーネントのインタフェースとして利用される。ポートはDataオブジェクトで定義され、方向を定義すr。

Chiselは方向を定義するためのオブジェクトを用意している。

class Decoupled extends Bundle {
  val ready = Output(Bool())
  val data  = Input(UInt(32.W))
  val valid = Input(Bool())
}

Decoupledは新しい方となり複数のモジュール間のインタフェースとして利用することができる。

モジュール

ChiselはVerilogのモジュール同様に階層的な構造を作成することができる。

階層的なモジュールの名前空間はデバッガ上で階層的に参照することができる。 ユーザの定義したモジュールは「クラス」として定義される。

  • Moduleクラスを継承する。
  • モジュールのIO()メソッドでインタフェースをラップする。また、ioという変数にポートフィールドを格納する。

例として、2入力のマルチプレクサの定義を考えてみる。

class Mux2 extends Module {
  val io = IO(new Bundle{
    val sel = Input(UInt(1.W))
    val in0 = Input(UInt(1.W))
    val in1 = Input(UInt(1.W))
    val out = Output(UInt(1.W))
  })
  io.out := (io.sel & io.in1) | (~io.sel & io.in0)
}

インタフェースの定義はBundleを使って定義する。モジュールのインタフェースはフィールド名ioによって定義される。 Mux2では、ioは4つのフィールドで定義され、それぞれマルチプレクサのポートである。

演算子 := はアサイン演算子であり、モジュール定義の中で利用される。Chiselの特殊な演算子であり、左項の配線と右項の配線を接続するための構文である。

モジュールの階層化

回路を改装設計するにあたり、より大きなモジュールを、より小さなモジュールを組み合わせて作成することができる。 以下の例は4入力のマルチプレクサモジュールを、2入力のマルチプレクサMux2を3つ使用して作成している。

class Mux4 extends Module {
  val io = IO(new Bundle {
    val in0 = Input(UInt(1.W))
    val in1 = Input(UInt(1.W))
    val in2 = Input(UInt(1.W))
    val in3 = Input(UInt(1.W))
    val sel = Input(UInt(2.W))
    val out = Output(UInt(1.W))
  })
  val m0 = Module(new Mux2)
  m0.io.sel := io.sel(0) 
  m0.io.in0 := io.in0
  m0.io.in1 := io.in1

  val m1 = Module(new Mux2)
  m1.io.sel := io.sel(0) 
  m1.io.in0 := io.in2
  m1.io.in1 := io.in3

  val m3 = Module(new Mux2)
  m3.io.sel := io.sel(1) 
  m3.io.in0 := m0.io.out
  m3.io.in1 := m1.io.out

  io.out := m3.io.out
}

ブラックボックス

Chiselは外部で定義されたモジュールを「ブラックボックス」として取り扱うことができる。 例えばChiselで定義されていないようなFPGAのIPなどを接続するために便利である。

BlackBoxで定義されたモジュールはVerilogなどで定義されているモジュールであるが、そのモジュールの動作を定義するようなコードは生成されない。

パラメータ化

この機能は実験的なものであり、APIの変更が発生する可能性があります。

BlackBoxパラメータに、Verilogのパラメータを渡すことができる。例えば、XilinxのDifferent Clock Buffer (IBUFDS)をChiselでインスタンス化することを考える。

import chisel3._
import chisel3.experimental._ // To enable experimental features

class IBUFDS extends BlackBox(Map("DIFF_TERM" -> "TRUE",
                                  "IOSTANDARD" -> "DEFAULT")) {
  val io = IO(new Bundle {
    val O = Output(Clock())
    val I = Input(Clock())
    val IB = Input(Clock())
  })
}

Chiselの生成したVerilogコードであh、IBUFDSは以下のように定義されている。

IBUFDS #(.DIFF_TERM("TRUE"), .IOSTANDARD("DEFAULT")) ibufds (
  .IB(ibufds_IB),
  .I(ibufds_I),
  .O(ibufds_O)
);

ブラックボックスに対して実装を提供する

以下の例は2つの実数を加算して返すブラックボックスモジュールだ。数値は64ビット符号なしのChisel整数として定義されている。

class BlackBoxRealAdd extends BlackBox {
  val io = IO(new Bundle() {
    val in1 = Input(UInt(64.W))
    val in2 = Input(UInt(64.W))
    val out = Output(UInt(64.W))
  })
}

実装は、以下のVerilogで定義されている。

module BlackBoxRealAdd(
    input  [63:0] in1,
    input  [63:0] in2,
    output reg [63:0] out
);
  always @* begin
  out <= $realtobits($bitstoreal(in1) + $bitstoreal(in2));
  end
endmodule

リソースファイル中のVerilogを使ったブラックボックス

バックエンドのシミュレータにVerilogのコードを渡すために、Chisel3はchise/firrtl アノテーションシステムをベースにしたツールを提供している。

ブラックボックスモジュールの定義に HasBlackBoxResourceを追加することにより、システムがVerilogファイルを見つけて関数を呼び出すことができる。モジュールは以下のように定義することができる。

class BlackBoxRealAdd extends BlackBox with HasBlackBoxResource {
  val io = IO(new Bundle() {
    val in1 = Input(UInt(64.W))
    val in2 = Input(UInt(64.W))
    val out = Output(UInt(64.W))
  })
  setResource("/real_math.v")
}

インラインVerilogを使用したブラックボックス

Verilog記述をScala中に直接記述することでブラックボックスを定義することもできる。 HasBlackBoxResourceの代わりに、HasBlackBoxInlineを指定し、setResourceの代わりにsetInlineを指定することで実現できる。

class BlackBoxRealAdd extends BlackBox with HasBlackBoxResource {
  val io = IO(new Bundle() {
    val in1 = Input(UInt(64.W))
    val in2 = Input(UInt(64.W))
    val out = Output(UInt(64.W))
  })
  setInline("BlackBoxRealAdd.v",
    s"""
      |module BlackBoxRealAdd(
      |    input  [15:0] in1,
      |    input  [15:0] in2,
      |    output [15:0] out
      |);
      |always @* begin
      |  out <= $realtobits($bitstoreal(in1) + $bitstoreal(in2));
      |end
      |endmodule
    """.stripMargin)
}

内部構成について

Verilogファイルと連結してバックエンドと接続する機構は、Chisel/Firrtlアノテーションによって実現している。 inlineとresrouceの2つのアノテーションは、現在はsetInlineもしくはsetResourceメソッドを呼び出すことで実現している。 これらのアノテーションはchisel-testsを通じてfirrtlに順番に渡される。 デフォルトのfirrtl verilogコンパイラアノテーションを検出する機構を持っており、ファイルもしくはインラインコードをビルドディレクトリに渡す。 渡された各ファイルはblack_box_verilog_files.f に追記される。 このファイルはVerilatorもしくはVCSに渡されるファイルリストである。 dsptools projectは、実数のシミュレーションをビルドするためにこの機構を使用している良い例である。

インタプリタ

firrtl インタプリタは、ユーザがブラックボックスScala実装を行うことができるように分離したシステムを利用している。 Scalaの実装コードはBlackBoxFactoryを利用して、実行ハーネスに渡される。 インタプリタScalaのシミュレーションテスタである。 上記で紹介したdsptools projectはこの機構を使用しているプロジェクトの良い例である。

前回の記事はこちら。

msyksphinz.hatenablog.com