関数の定義
デザインの再利用のために関数を定義することができる。
def clb(a: UInt, b: UInt, c: UInt, d: UInt): UInt =
(a & b) | (~c & d)
a, b, c, dの引数を取って、論理演算を実行した結果を返す。
BundleとVec
Bundle
とVec
によって、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
から派生しているクラスである。
Bundle
とVec
はネストより複雑なデータ型を作ることができる。
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はこの機構を使用しているプロジェクトの良い例である。
前回の記事はこちら。