Chiselのコンパイルフローの解析の続き。前の記事は以下。
例えば、Chiselでは以下のようにして入力信号を出力信号につなげる記述が可能になる。
val io = IO(new Bundle { val in = Input(Bool()) val out = Output(Bool()) }) io.out := io.in
この:=
という代入演算子は、Scalaで定義されたものではない。Scalaは演算子のオーバーロードを行うことができ、Chisel向けに作られた代入演算子である。
これによりChiselのハードウェアデータ型は、互いに接続することが可能になる。
Chiselのハードウェアデータ型は、UInt
, SInt
, Bool
が定義されており、それぞれのデータはChisel内のBits.scala
に定義がある。
chisel3/ChiselFrontend/src/main/scala/chisel3/Bits.scala
sealed class UInt private[chisel3] (width: Width) extends Bits(width) with Num[UInt] { sealed class SInt private[chisel3] (width: Width) extends Bits(width) with Num[SInt] { sealed class Bool() extends UInt(1.W) with Reset {
要するに、UInt
, SInt
はBits
クラスを継承しており、Bool
はUInt
を継承している。
Bits
というクラスを継承しているので、その定義を確認してみる。
/** A data type for values represented by a single bitvector. This provides basic bitwise operations. * * @groupdesc Bitwise Bitwise hardware operators * @define coll [[Bits]] * @define sumWidthInt @note The width of the returned $coll is `width of this` + `that`. * @define sumWidth @note The width of the returned $coll is `width of this` + `width of that`. * @define unchangedWidth @note The width of the returned $coll is unchanged, i.e., the `width of this`. */ sealed abstract class Bits(private[chisel3] val width: Width) extends Element with ToBoolable { //scalastyle:off number.of.methods
Bits
のさらに継承元のクラスであるData
にて、:=
演算子が定義されていることが分かる。
chisel3/chiselFrontend/src/main/scala/chisel3/Data.scala
/** Connect this $coll to that $coll mono-directionally and element-wise. * * This uses the [[MonoConnect]] algorithm. * * @param that the $coll to connect to * @group Connect */ final def := (that: Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = this.connect(that)(sourceInfo, connectionCompileOptions) // scalastyle:ignore line.size.limit
さて、実装を見てみると、Data
クラス内のthis.connect()
を読んでおり、実体は同じData
クラス内のData::connect()
である。
private[chisel3] def connect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = { // scalastyle:ignore line.size.limit
さらに内部をのぞいてみると、MonoConnect.connect()
という関数が呼ばれている。これはつまり一方方向の接続だ。ついでに見てみると、BulkConnect.connect()
という関数も存在しており、これはA <> B
のための実装だと思われる。
chisel3/chiselFrontend/src/main/scala/chisel3/internal/MonoConnect.scala
private[chisel3] object MonoConnect { ... /** This function is what recursively tries to connect a sink and source together * * There is some cleverness in the use of internal try-catch to catch exceptions * during the recursive decent and then rethrow them with extra information added. * This gives the user a 'path' to where in the connections things went wrong. */ def connect( //scalastyle:off cyclomatic.complexity method.length sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, sink: Data, source: Data, context_mod: RawModule): Unit = (sink, source) match { // Handle legal element cases, note (Bool, Bool) is caught by the first two, as Bool is a UInt case (sink_e: Bool, source_e: UInt) => elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: UInt, source_e: Bool) => elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: UInt, source_e: UInt) => ...
あとは、変数の型によってどのような接続を行うのか記述してある。elemConnect
を見てみると、
def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, sink: Element, source: Element, context_mod: RawModule): Unit = { // scalastyle:ignore line.size.limit import BindingDirection.{Internal, Input, Output} // Using extensively so import these ... // CASE: Context is same module that both left node and right node are in if( (context_mod == sink_mod) && (context_mod == source_mod) ) { ((sink_direction, source_direction): @unchecked) match { // SINK SOURCE // CURRENT MOD CURRENT MOD case (Output, _) => issueConnect(sink, source) case (Internal, _) => issueConnect(sink, source) case (Input, _) => throw UnwritableSinkException } }
issueConnect()
が呼ばれる。最終的にissueConnect
はpushCommand
を呼び出し、この接続をハードウェアコマンドとして登録するわけだ。
chisel3/chiselFrontend/src/main/scala/chisel3/internal/Builder.scala
private def issueConnect(sink: Element, source: Element)(implicit sourceInfo: SourceInfo): Unit = { // If the source is a DontCare, generate a DefInvalid for the sink, // otherwise, issue a Connect. source.topBinding match { case b: DontCareBinding => pushCommand(DefInvalid(sourceInfo, sink.lref)) case _ => pushCommand(Connect(sourceInfo, sink.lref, source.ref)) } }
まとめると、以下のようになる。