前回の続きでChiselの文法入門編その3
前回の冒頭に書いたChiselにおける3つ目の要素であるChiselのハードウェアの要素について見ていきます。
Chisel入門編〜その3:Chiselのハードウェアの要素〜
Chiselの”型”と”ハードウェアの要素”の区別
前回基本のデータ型を確認するために作ったモジュールを一部分抜粋して再掲します。
この中にコメントアウトした部分が存在するのがわかるかと思います。
/** * Chiselの基本の型に関する確認 */ class TestChiselTypes extends Module { val io = IO(new Bundle {}) val boolA = Bool() val boolB = true.B //val boolB = boolA & true.B // こちらを有効にするとエラー発生 val boolC = false.B // printlnを使うとエラボレート時に型の情報が出力される println(boolA) println(boolB) println(boolC) // printfでシミューション実行時の値を出力可能 // printf("boolA : %d\n", boolA) // これはエラーになる printf("boolB : %d\n", boolB) printf("boolC : %d\n", boolC) }
これがエラーになる理由がを理解する上で必要なのがChiselを書く上で意識しておくべき3つ目の境界線になります。
まずは、上記のコメントアウトを外した状態でエラボレートした際にどんなエラーが出るかを確認してみます。
この記述Scala/Chiselの文法的には正しいので、とりあえずエラボレート自体は成功しますがその後のハードの構築時にエラーが発生します。
[info] Compiling 1 Scala source to /home/diningyo/prj/study/2000_chisel/500_learning-chisel3/subprj/chisel-basic/target/scala-2.11/classes ... [info] Done compiling. [warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list [info] Packaging /home/diningyo/prj/study/2000_chisel/500_learning-chisel3/subprj/chisel-basic/target/scala-2.11/chiselbasic_2.11-2.0.jar ... [info] Done packaging. [info] Running ElaborateTestChiselTypes [info] [0.002] Elaborating design... chisel3.core.Bool@11 chisel3.core.Bool@12 chisel3.core.Bool@13 chisel3.core.UInt@1d chisel3.core.UInt@1e - bit width = 9 chisel3.core.UInt@1f - bit width = 32 chisel3.core.SInt@20 chisel3.core.SInt@21 32 [info] [1.210] Done elaborating. [error] (run-main-20) firrtl.passes.CheckChirrtl$UndeclaredReferenceException: @[TestChiselTypes.scala 25:9:@11.6]: [module TestChiselTypes] Reference boolA is not declared. [error] firrtl.passes.CheckChirrtl$UndeclaredReferenceException: @[TestChiselTypes.scala25:9:@11.6]: [module TestChiselTypes] Reference boolA is not declared.
さてエラーの内容ですが
- ”
boolA
のリファレンスが宣言されていません”
となっています。
boolA
は宣言自体は存在しているのになぜ??という話ですが、これが先ほど書いた3つ目の境界線であるChiselの型とChiselのハードウェア要素の違いになります。
ちょっとエラー内容がわかりにくいので、少し記述を変えて分かりやすいエラーに変更してみます。
変更点はboolB
をboolA
との論理積に変更したことにあります。
val boolA = Bool() val boolB = boolA & true.B
この状態でエラボレートを実行してエラーを確認してみます。(&多分通常見かけるのはこちらのエラーの方が多いと思います)
[error] (run-main-23) chisel3.core.Binding$ExpectedHardwareException: bits operated on 'chisel3.core.Bool@11' must be hardware, not a bare Chisel type. Perhaps you forgot to wrap it in Wire(_) or IO(_)? [error] chisel3.core.Binding$ExpectedHardwareException: bits operated on 'chisel3.core.Bool@11' must be hardware, not a bare Chisel type. Perhaps you forgot to wrap it in Wire(_) or IO(_)?
今度はもう少し親切なログが表示されていて
- ”
chisel3.core.Bool@11
は素のChiselの型ではなくハードウェアでなければなりません。Wire(_)
もしくはIO(_)
でのラップを忘れていませんか?”
となっています。
大体はこのエラーメッセージが教えてくれていますがChiselでは”型”と”ハードウェア要素”は実際のハードウェア(FIRRTLレベル)を生成する上で明確に区別しておく必要があります。
Chiselのハードウェア要素
ということで、Chiselのハードウェアを要素についてです。
Chiselのハードウェアの要素としては大きく分けて以下の4つがあります(括弧の中はVerilog HDLで対応する要素を記載しました)。
- Module : これはそのままモジュール(module)
- IO : モジュールのIOポート宣言(input/output)
- Wire : 組み合わせ回路用のハードウェア構成要素(wire)
- Reg : 順序回路用のハードウェア構成要素(reg)
これらのChiselのハードウェア要素を示すキーワードを使ってChiselのデータ型のインスタンスをラップしてやると、その変数や値はFIRRTLに変換可能なハードウェア表現として扱われるようになります。
ここまでにもあまり詳細には触れずに使ってきていますが、IOポートを宣言するために以下のような記述を使っていました。
val io = IO(new Bundle { val in = Input(Bool()) val out = Output(Bool()) })
この記述もBundle
という型を使って作ったChiselの型のインスタンスをIO(_)
でくるむことによってChiselのハードウェア要素に変換しています(IO(_)
は_
に与えられたChiselの型のインスタンスをモジュールのIO端子に変換します)。
ではもう少し、各要素について見ていくことにします。
Module
Verilog HDLで言うmodule
に相当するChiselのハードウェア構成要素です。
ご存知の通り、論理回路は特定の目的を達成するために特定の機能を持つ論理回路を実装したモジュールを組み合わせることで、より大きな機能を実現していきます。Module
はこの”特定の機能”を区別するための境界になっています。
先に記載したとおりChiselのハードウェア構成要素はそのキーワードを使って、作成したデータ型のインスタンスをラップすることでChiselのハードウェアとして実体化させることが出来ます。
文章で書くと難しく見えるかもしれませんが、要は以下のようにすることを意味しています。
class ModuleA extends Module { val io = IO(new Bundle { val in = Input(Bool()) val out = Output(Bool()) }) io.out := io.in } class ModuleTop extends Module { val io = IO(new Bundle { val in = Input(Bool()) val out = Output(Bool()) }) // Module(_)でModuleAのインスタンスをラップ // これによりModuleTop内にModuleAがChiselの // ハードウェアとして実体化する val modA = Module(new ModuleA) modA.io.in := io.in io.out := modA.io.out }
IO
これについては既に出てきているので簡単に。
class ModuleA extends Module { // IO(_)でBundleのインスタンスをくるむ val io = IO(new Bundle { val in = Input(Bool()) val out = Output(Bool()) }) io.out := io.in }
Wire
Wire
はそのままVerilog HDLのwire
にマッピングされるChiselのハードウェア構成要素です。
使い方は先のModule
/IO
と同様で組み合わせ回路として扱いたいChiselのデータ型のインスタンスを以下のようにWire
でくるむだけです。
val <変数名> = Wire(<Chiselのデータ型のインスタンス>)
これを使うことで本記事の最初に取り上げたval boolA = Bool()
のエラーを解消することが出来ます。
興味があれば、冒頭のソースコードのエラーになったboolA
をWire(_)
でくるんでエラボレートしてみてください。エラーの内容が以下のような物に変わることが確認できるはずです(Wire(Bool())
にしただけだと初期化されていないので値が確定しないため)。
[error] (run-main-6) firrtl.passes.CheckInitialization$RefNotInitializedException: @[TestChiselTypes.scala 13:19:@8.4] : [module TestChiselTypes] Reference _T_5 is not fully initialized. [error] : _T_5 <= VOID
なお既に以下のようにChiselのハードウェアとして扱われている変数を使って新しい変数を作るような場合にはWire
によるラッピングは不要になります。
val wireA = Wire(Bool()) val wireB = wireA // Wire(_)は不要
因みにエラーは冒頭のエラーとは全く逆で以下のようなものになります。
[error] (run-main-2d) chisel3.core.Binding$ExpectedChiselTypeException: wire type 'chisel3.core.Bool@28' must be a Chisel type, not hardware [error] chisel3.core.Binding$ExpectedChiselTypeException: wire type 'chisel3.core.Bool@28' must be a Chisel type, not hardware
Reg
続いてはReg
です。これも名前から簡単に推測できると思いますがVerilog HDLにおけるreg
に対応するChiselのハードウェア構成要素です。
使い方は宣言が変わる以外は全くWire
と同様になります。
val <変数名> = Reg(<Chiselのデータ型のインスタンス>)
Verilog HDLのreg
にマッピングされると書いたとおり、Reg
で宣言した変数はRTLに変換すると
always @(posedge clock) begine // 所望の論理 end
となります。
Reg
の派生系RegInit
/RegNext
さて、これでChiselでレジスタを宣言できるようになったのですがReg
を変換したRTLを見てリセットが無いことに気づいた方もいらっしゃると思います。
実はChiselのレジスタには以下の3種類が存在していて、実装者の意図に応じて使い分けることが出来ます。
Reg
については既に書いたとおりなので、残りの2つの使い方を見ていきます。
RegInit
まずはリセット付きのレジスタRegInit
ですが、使い方は以下のようになります。
val <変数名> = RegInit(<初期値(Chiselの定数表現)>)
Chiselの定数表現と書いたのは、前回に書いたBool
/UInt
/SInt
における以下のような記述を指しています。
val boolA = true.B val uintA = 0x10.U val sintA = 0x10.S
これをRegInit
でくるむため、上記のboolA
/uintA
/sintA
の値でリセットを行うレジスタを生成する記述は以下のようになります。
val regBoolA = RegInit(true.B) val regUintA = RegInit(0x10.U) // 定数を直接記載してもいいし val sintA = RegInit(sintA) // 定数を格納した変数を入れてもOK val init = 0x0 // ScalaのIntで宣言しておいて val initReg = RegInit(init.U) // レジスタ作成時にUIntに変換するのもOK
リセット付きのレジスタなので、RTLを生成すると以下のような記述に変換されます。
always @(posedge clock) begine if (reset) begin <変数名> <= <初期値> end // 所望の論理 end
なお、ホントはもうひとつ宣言方法があるんですが使用頻度が少ないと思いますので割愛します。
興味ある方はChiselのソースコードをご覧ください。
- src/main/scala/chisel3/Reg.scala github.com
RegNext
"Next"とつく通り、何か既にChiselのハードウェア構成要素の表現をRegNext
に入れることで1cycle遅延した信号を作ることが出来ます。このRegNext
はリセット時の初期化あり/なしで2つの宣言の仕方があります。
val <変数名> = RegNext(<既存のChiselハードウェア構成要素>) // リセット時の初期化なし val <変数名> = RegNext(<既存のChiselハードウェア構成要素>, <初期値>) // リセット時の初期化あり
例えば以下のような感じになります。
val boolA = true.B val boolRegNextA = RegNext(boolA)
Chiselのハードウェア要素を使ったサンプル
なるべくシンプルにしたかったので、本記事で取り上げたModule
/IO
/Wire
/Reg
の要素を使った単なるディレイ・バッファを例にしました。
import chisel3._ /** * サブモジュール * 入力信号を1cycle遅延させるだけ */ class TestChiselHardwareA extends Module { val io = IO(new Bundle { val in = Input(Bool()) val out = Output(Bool()) }) val regNext = RegNext(io.in, false.B) io.out := regNext } /** * トップモジュール * これも入力を1cycle遅延させるだけ */ class TestChiselHardwareTop extends Module { val io = IO(new Bundle { val in = Input(Bool()) val out = Output(Bool()) }) val modA = Module(new TestChiselHardwareA) val wireIn = Wire(Bool()) val regInitOut = RegInit(true.B) // 信号を接続 // 解説してないけど、既にお気づきの通り // ":="で信号を接続出来る wireIn := io.in regInitOut := modA.io.out modA.io.in := wireIn io.out := regInitOut } /** * TestChiselHardwareTopのエラボレート */ object ElaborateTestChiselHardwareTop extends App { // RTL生成用 chisel3.Driver.execute(Array( "--target-dir=rtl/chisel-basic" ), () => new TestChiselHardwareTop ) }
生成されたVerilog HDLのRTL
以下のようになりました。
初期値をランダマイズするifdef
があって読みづらいのですが、そのまま掲載します。
追っていただくとわかりますが、wireIn
は単にio.in
を受けただけの信号なので、最適化によって信号自体が消失しています。
module TestChiselHardwareA( // @[:@3.2] input clock, // @[:@4.4] input reset, // @[:@5.4] input io_in, // @[:@6.4] output io_out // @[:@6.4] ); reg regNext; // @[TestChiselTypes.scala 85:24:@8.4] reg [31:0] _RAND_0; assign io_out = regNext; // @[TestChiselTypes.scala 87:10:@10.4] `ifdef RANDOMIZE_GARBAGE_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_INVALID_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_REG_INIT `define RANDOMIZE `endif `ifdef RANDOMIZE_MEM_INIT `define RANDOMIZE `endif `ifndef RANDOM `define RANDOM $random `endif `ifdef RANDOMIZE integer initvar; initial begin `ifdef INIT_RANDOM `INIT_RANDOM `endif `ifndef VERILATOR #0.002 begin end `endif `ifdef RANDOMIZE_REG_INIT _RAND_0 = {1{`RANDOM}}; regNext = _RAND_0[0:0]; `endif // RANDOMIZE_REG_INIT end `endif // RANDOMIZE // regNextがリセット付きでregになっている always @(posedge clock) begin if (reset) begin regNext <= 1'h0; end else begin regNext <= io_in; end end endmodule module TestChiselHardwareTop( // @[:@12.2] input clock, // @[:@13.4] input reset, // @[:@14.4] input io_in, // @[:@15.4] output io_out // @[:@15.4] ); wire modA_clock; // @[TestChiselTypes.scala 101:20:@17.4] wire modA_reset; // @[TestChiselTypes.scala 101:20:@17.4] wire modA_io_in; // @[TestChiselTypes.scala 101:20:@17.4] wire modA_io_out; // @[TestChiselTypes.scala 101:20:@17.4] reg regInitOut; // @[TestChiselTypes.scala 103:27:@21.4] reg [31:0] _RAND_0; // Moduleが実体化した TestChiselHardwareA modA ( // @[TestChiselTypes.scala 101:20:@17.4] .clock(modA_clock), .reset(modA_reset), .io_in(modA_io_in), .io_out(modA_io_out) ); assign io_out = regInitOut; // @[TestChiselTypes.scala 112:10:@25.4] assign modA_clock = clock; // @[:@18.4] assign modA_reset = reset; // @[:@19.4] assign modA_io_in = io_in; // @[TestChiselTypes.scala 111:14:@24.4] `ifdef RANDOMIZE_GARBAGE_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_INVALID_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_REG_INIT `define RANDOMIZE `endif `ifdef RANDOMIZE_MEM_INIT `define RANDOMIZE `endif `ifndef RANDOM `define RANDOM $random `endif `ifdef RANDOMIZE integer initvar; initial begin `ifdef INIT_RANDOM `INIT_RANDOM `endif `ifndef VERILATOR #0.002 begin end `endif `ifdef RANDOMIZE_REG_INIT _RAND_0 = {1{`RANDOM}}; regInitOut = _RAND_0[0:0]; `endif // RANDOMIZE_REG_INIT end `endif // RANDOMIZE always @(posedge clock) begin if (reset) begin regInitOut <= 1'h1; end else begin regInitOut <= modA_io_out; end end endmodule
そろそろ演算子や制御構造の話を書かないと、、、と思いながらその4に続く。