FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

ハードウェア記述言語Chiselコンパイラの内部解析(6. もう少し真面目にChiselの型を追加するコードを書く)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181105/20181105012126.png

前回は少し手を抜いたような形で新しいデータ型を追加してみたが、少し真面目にデータ型の追加方法について検討してみる。

前回も書いたが、UInt, SInt, Boolが定義されており、さらに良く調べてリムとexperimentalFixedPointが定義されている。

これはBitsクラスをベースにして拡張されている。前回のTInt型はUIntを継承する形で定義したが、今回はまじめにBits型を継承する形で追加してみる。

f:id:msyksphinz:20190913142122p:plain
TInt型をBits型を継承して定義してみる。
  • chiselFrontend/src/main/scala/chisel3/package.scala

TInt型を便利使うための様々なルーチンを定義する。例えば、0.T(10.W)で10ビットのTInt型を定義するなどが可能になる。 あとは、asTIntなどを定義して、他の型からTInt型に変換できるようにしてみる。

diff --git a/chiselFrontend/src/main/scala/chisel3/package.scala b/chiselFrontend/src/main/scala/chisel3/package.scala
index bd724369..1ebb3afb 100644
--- a/chiselFrontend/src/main/scala/chisel3/package.scala
+++ b/chiselFrontend/src/main/scala/chisel3/package.scala
@@ -37,12 +37,18 @@ package object chisel3 {    // scalastyle:ignore package.object.name
         /** Int to UInt conversion, recommended style for constants.
           */
         def U: UInt = UInt.Lit(bigint, Width())  // scalastyle:ignore method.name
+        /** Int to TInt conversion, recommended style for constants.
+          */
+        def T: TInt = TInt.Lit(bigint, Width())  // scalastyle:ignore method.name
         /** Int to SInt conversion, recommended style for constants.
           */
         def S: SInt = SInt.Lit(bigint, Width())  // scalastyle:ignore method.name
         /** Int to UInt conversion with specified width, recommended style for constants.
           */
         def U(width: Width): UInt = UInt.Lit(bigint, width)  // scalastyle:ignore method.name
+        /** Int to TInt conversion with specified width, recommended style for constants.
+          */
+        def T(width: Width): TInt = TInt.Lit(bigint, width)  // scalastyle:ignore method.name
         /** Int to SInt conversion with specified width, recommended style for constants.
           */
         def S(width: Width): SInt = SInt.Lit(bigint, width)  // scalastyle:ignore method.name
@@ -50,12 +56,18 @@ package object chisel3 {    // scalastyle:ignore package.object.name
         /** Int to UInt conversion, recommended style for variables.
           */
         def asUInt(): UInt = UInt.Lit(bigint, Width())
+        /** Int to TInt conversion, recommended style for variables.
+          */
+        def asTInt(): TInt = TInt.Lit(bigint, Width())
         /** Int to SInt conversion, recommended style for variables.
           */
         def asSInt(): SInt = SInt.Lit(bigint, Width())
         /** Int to UInt conversion with specified width, recommended style for variables.
           */
         def asUInt(width: Width): UInt = UInt.Lit(bigint, width)
+        /** Int to TInt conversion with specified width, recommended style for variables.
+          */
+        def asTInt(width: Width): TInt = TInt.Lit(bigint, width)
         /** Int to SInt conversion with specified width, recommended style for variables.
           */
         def asSInt(width: Width): SInt = SInt.Lit(bigint, width)

次に、実体を定義してみる。実体の定義は、UIntSIntと同様にすべての演算子の挙動を定義する必要がある。 実装が必要なものをリストアップしてみると、結構ある。ただし殆どのものはUIntのものをコピーしてくればよい。

気を付けなければならないのは他の型との演算の場合など。下記の演算では、zexit()でいったんSIntに変換してから演算を適用してから、最後にTIntに変更する。

  final def * (that: UInt): TInt = macro SourceInfoTransform.thatArg
  /** @group SourceInfoTransformMacro */
  def do_* (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): TInt = {
    val thatToSInt = that.zext()
    val result = binop(sourceInfo, SInt(this.width + thatToSInt.width), TimesOp, thatToSInt)
    result.tail(1).asTInt
  }

上記で出てきているmacro SourceInfoTransform.thatArgだが、これはどうもマクロらしい?thatArg節が定義されているコードを調べると定義が出てくる。

// Workaround for https://github.com/sbt/sbt/issues/3966
object SourceInfoTransform
class SourceInfoTransform(val c: Context) extends AutoSourceTransform {
...
  def thatArg(that: c.Tree): c.Tree = {
    q"$thisObj.$doFuncTerm($that)($implicitSourceInfo, $implicitCompileOptions)"
  }

つまりここで自分のオブジェクトに対してdoFuncTermを呼び出している。doFuncTermは、

  • coreMacros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala
abstract class AutoSourceTransform extends SourceInfoTransformMacro {
  import c.universe._
  /** Returns the TermName of the transformed function, which is the applied function name with do_
    * prepended.
    */
  def doFuncTerm: TermName = {
    val funcName = c.macroApplication match {
      case q"$_.$funcName[..$_](...$_)" => funcName
      case _ => throw new Exception(s"Chisel Internal Error: Could not resolve function name from macro application: ${showCode(c.macroApplication)}") // scalastyle:ignore line.size.limit
    }
    TermName("do_" + funcName)
  }
}

これはつまりdef *def do_*に変換する処理が行われている、と思う。

さらに、FIRRTLのEmitterを改造して、TInt型がMatchするように変更する。

  • src/main/scala/chisel3/internal/firrtl/Emitter.scala
  private def emitType(d: Data, clearDir: Boolean = false): String = d match { // scalastyle:ignore cyclomatic.complexity line.size.limit
    case d: Clock => "Clock"
    case _: AsyncReset => "AsyncReset"
    case _: ResetType => "Reset"
    case d: chisel3.core.EnumType => s"UInt${d.width}"
    case d: UInt => s"UInt${d.width}"
    case d: SInt => s"SInt${d.width}"
    case d: TInt => s"TInt${d.width}"
...

もう一つ、FIRRTLに渡すためにConverterのextractTypeを追加する。FIRRTLにTInt型を追加していないので、今回はとりあえずTIntUIntに変換して扱う。

  • chiselFrontend/src/main/scala/chisel3/internal/firrtl/Converter.scala
  def extractType(data: Data, clearDir: Boolean = false): fir.Type = data match { // scalastyle:ignore cyclomatic.complexity line.size.limit
    case _: Clock => fir.ClockType
    case _: AsyncReset => fir.AsyncResetType
    case _: ResetType => fir.ResetType
    case d: EnumType => fir.UIntType(convert(d.width))
    case d: UInt => fir.UIntType(convert(d.width))
    case d: TInt => fir.UIntType(convert(d.width))  // msyksphinz : Temporary
...

という訳で、上記のコードをコンパイルして使用してみる。

sbt publishLocal

コンパイルしたバイナリを使用してChiselのプログラムをコンパイルしてみる。

package tint_test

import chisel3._

class tint_test extends Module {
  val io = IO(new Bundle {
    val in1 = Input(TInt(8.W))
    val in2 = Input(TInt(8.W))
    val out = Output(TInt(8.W))
  })

  io.out := io.in1 * io.in2
}

object main extends App {
  chisel3.Driver.emitVerilog(new tint_test())
}

コンパイル後は以下のようなVerilogが生成された。

module tint_test(
  input        clock,
  input        reset,
  input  [7:0] io_in1,
  input  [7:0] io_in2,
  output [7:0] io_out
);
  wire [15:0] _T; // @[tint_test.scala 12:20]
  assign _T = io_in1 * io_in2; // @[tint_test.scala 12:20]
  assign io_out = _T[7:0]; // @[tint_test.scala 12:10]
endmodule