ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Chiselの文法 - 入門編〜その3:Chiselのハードウェアの要素〜

スポンサーリンク

前回の続きで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のハードウェア要素の違いになります。

ちょっとエラー内容がわかりにくいので、少し記述を変えて分かりやすいエラーに変更してみます。
変更点はboolBboolAとの論理積に変更したことにあります。

  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()のエラーを解消することが出来ます。
興味があれば、冒頭のソースコードのエラーになったboolAWire(_)でくるんでエラボレートしてみてください。エラーの内容が以下のような物に変わることが確認できるはずです(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のソースコードをご覧ください。

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に続く。

www.tech-diningyo.info