前回の続きでChiselの文法入門編その4
4回目はChiselの各演算子について。
Chisel入門編〜その4:Chiselの演算子〜
これについては”Chiselではこう書く”という文法のお話になるので、Verilog HDLの記述と見比べつつ各種演算について記載していきます。
挙動を紹介する必要のある部分については、コンパイル可能なモジュール形式でのソースを記載し、結果についても適宜補足を行っていきます。
まず紹介を始める前に、言葉の定義をしておきます。
- 定数値:Chiselの定数表現。例えば
false.B
や0.U
のようなものを指します - ハードウェア要素:Chiselのハードウェアの要素。前回紹介したとおりで
Moudle
/IO
/Wire
/Reg
(及びその派生種)を指しますが、ここではModule
は除きます。
代入(というか接続と言ったほうがいいかも)
- Verilog HDL
wire a; reg b; assgin a = 1'b1; alwasy @(posedge clk or negedge rst) begin if (!rst) begin b <= 1'b0; end else begin b <= 1'b1; end end
- Chisel
val a = Wire(Bool()) a := false.B // 定数値を接続/WireなのでRTL変換時にはassignに変換されます val b = Reg(Bool()) b := a // ハードウェア要素の接続/Regなのでalwaysブロック内でノンブロッキング代入になります
四則演算(剰余も含む)
これについては基本的にはVerilog HDLと一緒だが、加算/減算にはオーバーフロー/アンダーフロー発生時のビットの扱いに応じて2つの演算子が定義されている。
Verilog HDL
- 加算 :
a + 1'b1;
- 減算 :
a - 1'b1;
- 乗算 :
a * 2'b2;
- 除算 :
a / 2'b2;
- 剰余 :
a % 2'b2;
- 加算 :
Chisel
- 加算
- 通常 :
a + <ハードウェア要素 or 定数値>
- オーバーフロービット拡張 :
a +& <ハードウェア要素 or 定数値>
- 例えば8bit同士の加算をした際の結果を
val
で新しい変数に格納すると、新しい変数のビット幅が9bitになります。
- 例えば8bit同士の加算をした際の結果を
- 通常 :
- 減算 :
a - <ハードウェア要素 or 定数値>
- 通常 :
a - <ハードウェア要素 or 定数値>
- アンダーフロービット拡張 :
a -& <ハードウェア要素 or 定数値>
- 通常 :
- 乗算 :
a * <ハードウェア要素 or 定数値>
- 除算 :
a / <ハードウェア要素 or 定数値>
- 剰余 :
a % <ハードウェア要素 or 定数値>
- 加算
上記のChiselの演算をビット幅も込みで試してみたのが以下のソースコードになります。
class ArithOps(baseVal: Int) extends Module { val io = IO(new Bundle { val a = Input(UInt(8.W)) val b = Input(UInt(8.W)) }) val a = io.a val b = io.b val addInit = a + b val subInit = a - b val addInit2 = a +& b val subInit2 = a -& b val mulInit = a * b val divInit = a / b val modInit = a % b val addInit3 = Wire(UInt(8.W)) val subInit3 = Wire(UInt(8.W)) addInit3 := a +& b subInit3 := a -& b println(s"addInit Width = ${addInit.getWidth}") println(s"addInit2 Width = ${addInit2.getWidth}") println(s"addInit3 Width = ${addInit3.getWidth}") println(s"subInit Width = ${addInit.getWidth}") println(s"subInit2 Width = ${addInit2.getWidth}") println(s"subInit3 Width = ${addInit3.getWidth}") println(s"mulInit Width = ${mulInit.getWidth}") println(s"divInit2 Width = ${divInit.getWidth}") println(s"modInit Width = ${modInit.getWidth}") printf("addInit : %d\n", addInit) printf("subInit : %d\n", subInit) printf("mulInit : %d\n", mulInit) printf("divInit : %d\n", divInit) printf("modInit : %d\n", modInit) val add = a +& b val sub = a -& b val mul = a * b val div = a / b val mod = a & b printf("add : %d\n", add) printf("sub : %d\n", sub) printf("mul : %d\n", mul) printf("div : %d\n", div) printf("mod : %d\n", mod) }
- 実行結果
[info] [0.000] Elaborating design... addInit Width = 8 addInit2 Width = 9 // "+&"を使っているのでbit幅が9bit addInit3 Width = 8 // "+&"を使っているが変数定義自体はUInt(8.W)なのでbit幅は8bit subInit Width = 8 subInit2 Width = 9 // -&も一緒 subInit3 Width = 8 // -&も一緒 mulInit Width = 16 divInit2 Width = 8 modInit Width = 8 [info] [0.049] Done elaborating. Total FIRRTL Compile Time: 15.9 ms Total FIRRTL Compile Time: 11.4 ms End of dependency graph Circuit state created [info] [0.000] SEED 1559360234623 // a == 1 / b == 2の結果 addInit : 3 subInit : 255 // 1 - 2 -> -1 = 0xff mulInit : 2 divInit : 0 modInit : 1 add : 3 sub : 511 mul : 2 div : 0 mod : 0
比較
以下の4つについてはVerilog HDLとChiselで共通
- Verilog HDL/Chisel
- より大きい :
a > b
- 以上 :
a >= b
- より小さい :
a < b
- 以下 :
a <= b
- より大きい :
”等しい”と"等しくない"はVerilog HDLとは異なります。
- Verilog HDL
- 等しい :
a == b
- 等しくない :
a != b
- 等しい :
- Chisel
- 等しい :
a === b
- 等しくない :
a =/= b
// 実は!=
もあるにはあるがdeprecated扱いなので使わないほうがよいです
- 等しい :
以下のコードは各種演算結果の戻り値の型を確認するためのものです。
class CompareOps extends Module { val io = IO(new Bundle { val a = Input(UInt(8.W)) val b = Input(UInt(8.W)) }) val a = io.a val b = io.b val equal = a === b val notEqual = a =/= b val lessThan = a < b val greaterThan = a > b val lessThanEqual = a <= b val greaterThanEqual = a >= b println(equal) println(notEqual) println(lessThan) println(greaterThan) println(lessThanEqual) println(greaterThanEqual) printf("equal : %b\n", equal) printf("notEqual : %b\n", notEqual) printf("lessThan : %b\n", lessThan) printf("greaterThan : %b\n", greaterThan) printf("lessThanEqual : %b\n", lessThanEqual) printf("greaterThanEqual : %b\n", greaterThanEqual) }
- 実行結果
// 以下のように戻り値は全てBoolになります [info] [0.000] Elaborating design... chisel3.core.Bool@11 chisel3.core.Bool@12 chisel3.core.Bool@13 chisel3.core.Bool@14 chisel3.core.Bool@15 chisel3.core.Bool@16 [info] [0.004] Done elaborating. Total FIRRTL Compile Time: 5.0 ms Total FIRRTL Compile Time: 4.0 ms End of dependency graph Circuit state created [info] [0.000] SEED 1559372898505 // io.a = 1 / io.b = 2 equal : 0 notEqual : 1 lessThan : 1 greaterThan : 0 lessThanEqual : 1 greaterThanEqual : 0
論理演算
論理演算ももほぼVerilog HDLとChiselで共通になります。
- 論理演算
- ビット単位の論理演算
リダクション演算についてはVerilog HDLとChiselで異なります。
- Verilog HDL
- Chisel("演算名+R"になる)
以下のコードはChiselの論理演算を試したものです。
class LogicalOps extends Module { val io = IO(new Bundle { val a = Input(Bool()) val b = Input(Bool()) val c = Input(UInt(8.W)) val d = Input(UInt(8.W)) }) val a = io.a val b = io.b val c = io.c val d = io.d val and = a && b val or = a || b val not = !a val xor = a ^ b //val bitwiseAnd = c && d // UIntなのでError //val bitwiseOr = c || d // UIntなのでError val bitwiseAnd = c & d val bitwiseOr = c | d val bitwiseNot = ~c val bitwiseXor = c ^ d val reductionAnd = c.andR val reductionOr = c.orR val reductionXor = c.xorR printf("and : %d'b%b\n", and.getWidth.U, and) printf("or : %d'b%b\n", or.getWidth.U, or) printf("not : %d'b%b\n", not.getWidth.U, not) printf("xor : %d'b%b\n", xor.getWidth.U, xor) printf("bitwiseAnd : %d'b%b\n", bitwiseAnd.getWidth.U, bitwiseAnd) printf("bitwiseOr : %d'b%b\n", bitwiseOr.getWidth.U, bitwiseOr) printf("bitwiseNot : %d'b%b\n", bitwiseNot.getWidth.U, bitwiseNot) printf("bitwiseXor : %d'b%b\n", bitwiseXor.getWidth.U, bitwiseXor) printf("reductionAnd : %d'b%b\n", reductionAnd.getWidth.U, reductionAnd) printf("reductionOr : %d'b%b\n", reductionOr.getWidth.U, reductionOr) printf("reductionXor : %d'b%b\n", reductionXor.getWidth.U, reductionXor) }
- 実行結果
// a = true.B / b = false.B / c = 0xaa.U / d = 0x55.U and : 1'b0 or : 1'b1 not : 1'b0 xor : 1'b1 bitwiseAnd : 8'b0 bitwiseOr : 8'b11111111 bitwiseNot : 8'b1010101 bitwiseXor : 8'b11111111 reductionAnd : 1'b0 reductionOr : 1'b1 reductionXor : 1'b0
連接
Verilog HDLでは以下のように定義されている各信号を束ねる処理を指します。
assign a = {<信号 or 定数値>, ... <信号 or 定数値>}
Chiselでは以下のいずれかの方法でビットの連接を実行します
Cat
を使う方法についてはいずれかの方法をでその時々に合わせて選択します。
val a = <ハードウェア要素 or 定数値> ## <ハードウェア要素 or 定数値> // これ今回Chiselのソース見てて気づいた。。。 val a = Cat(<ハードウェア要素 or 定数値>, .... , <ハードウェア or 定数値>) val a = Cat(Seq(<ハードウェア要素 or 定数値>, ... , <ハードウェア要素 or 定数値>))
なおCat
はchisel3.util
に存在するオブジェクトなのでインポートが必要になります。
実装は以下のようにobject
になっており、引数に応じて適切なapply
メソッドが呼び出されます。
以下の定義をみてもらうとわかるかと思いますが、どちらの形を使うにしても型パラメータがT <: Bits
のみなので、型の混在は出来ません。
object Cat { /** Concatenates the argument data elements, in argument order, together. The first argument * forms the most significant bits, while the last argument forms the least significant bits. */ def apply[T <: Bits](a: T, r: T*): UInt = apply(a :: r.toList) /** Concatenates the data elements of the input sequence, in reverse sequence order, together. * The first element of the sequence forms the most significant bits, while the last element * in the sequence forms the least significant bits. * * Equivalent to r(0) ## r(1) ## ... ## r(n-1). */ def apply[T <: Bits](r: Seq[T]): UInt = SeqUtils.asUInt(r.reverse) }
実際に挙動を試してみたのが以下のコードになります。
class CatOps extends Module { val io = IO(new Bundle { val a = Input(UInt(8.W)) val b = Input(UInt(8.W)) val c = Input(UInt(8.W)) val d = Input(UInt(8.W)) }) val a = io.a val b = io.b val c = io.c val d = io.d val cat0 = a ## b ## c ## d val cat1 = Cat(a, b, c, c) val cat2 = Cat(Seq(a, b, c, d)) //val cat2Error = Cat(a, Seq(b, c, d)) // 型の混在はNG val vec3 = WireInit(VecInit(Seq(a, b, c, d))) val cat3 = Cat(vec3) val vec4 = WireInit(VecInit(Seq(b, c, d))) //val cat4Error = Cat(a, vec4) // こちらもUIntとVec[UInt]で型が混在するのでNG printf("cat0 : %d'h%x\n", cat0.getWidth.U, cat0) printf("cat1 : %d'h%x\n", cat1.getWidth.U, cat1) printf("cat2 : %d'h%x\n", cat2.getWidth.U, cat2) printf("cat3 : %d'h%x\n", cat3.getWidth.U, cat3) }
- 実行結果
[info] [0.000] SEED 1559365774378 // 全て同じ結果が得られる cat1 : 32'h1020303 cat2 : 32'h1020304 cat3 : 32'h1020304
ビット選択
ビット選択の処理はChiselでは括弧が異なるだけ。これはScalaのcollection型の要素を指定する際の書式と一緒です。
ただ、サンプルコードに記載したとおりビット選択処理はChiselの処理的にはread-onlyの扱いになっているため、"sink"(データの宛先)に使うことが出来ません。
簡単に言うとa(0) := b(0)
という処理が出来ません。
- Verilog HDL
assign a = b[0]; assign a = b[9:0]; assign a[1] = b[0];
- Chisel
a := b(MSB<ハードウェア要素 or 定数値>[, LSB<ハードウェア要素 or 定数値>]) a := b.head(<n:Intでビット指定>) // MSBからnまでを取得(b(b.getWidth-1, n)と同じ) a := b.tail(<n:Intでビット指定>) // LSBからnまでを取得(b(n-1, 0)と同じ) // a(1) := b(0) // **これはChiselでは出来ない** // -> chisel3.internal.ChiselException: Cannot reassign to read-only chisel3.core.UInt@11
ということで上記の要素をまとめたコードは以下になります。
class BitSelOps extends Module { val io = IO(new Bundle { val a = Input(UInt(8.W)) }) val a = io.a val singleBit = a(7) val multiBits = a(7, 4) val headBits = a.head(4) val tailBits = a.tail(4) printf("singleBit : %d'h%x\n", singleBit.getWidth.U, singleBit) printf("multiBits : %d'h%x\n", multiBits.getWidth.U, multiBits) printf("headBits : %d'h%x\n", headBits.getWidth.U, headBits) printf("tailBits : %d'h%x\n", tailBits.getWidth.U, tailBits) }
- 実行結果
// io.a = 0xab singleBit : 1'h1 multiBits : 4'ha headBits : 4'ha tailBits : 4'hb
各種演算についてはここまで。次回その5は制御構文について↓