Chiselのアドバンスドな使い方はDiplomacy / Parameters / Configあたりを使いこなすことだと言われる。ParametersとConfigについては何となく理解しているつもりだが具体的なサンプルを使って確かめていなかったのでいくつか調査している。
最もシンプルな例はおそらくRISCV-Miniによる実装だろう。
https://github.com/ucb-bar/riscv-mini
src/main/scala/Config.scala
class MiniConfig extends Config((site, here, up) => { // Core case XLEN => 32 case Trace => true case BuildALU => (p: Parameters) => Module(new ALUArea()(p)) case BuildImmGen => (p: Parameters) => Module(new ImmGenWire()(p)) case BuildBrCond => (p: Parameters) => Module(new BrCondArea()(p)) // Cache case NWays => 1 // TODO: set-associative case NSets => 256 case CacheBlockBytes => 4 * (here(XLEN) >> 3) // 4 x 32 bits = 16B // NastiIO case NastiKey => new NastiParameters( idBits = 5, dataBits = 64, addrBits = here(XLEN)) } )
複数のパラメータがここで定義されている。これはConfigクラスを継承したもので、Configクラス自体はRocket-Chipに実装されている機能である(注意なのだがDiplomacyも含め、ConfigやParameterと言った機能はChiselやScalaそのものの機能ではなく、Scala上で構成されているプログラムに過ぎない)
これをモジュールのインスタンス化する際に渡してやることでモジュール内でこれらのパラメータを参照できるようになる。例えばRISCV-Miniでは以下のようにしてこのパラメータをモジュールに適用している。
src/main/scala/Main.scala
object Main extends App { val dir = new File(args(0)) ; dir.mkdirs // Configuration MiniConfigをインスタンス化してTileにパラメータとして渡している val params = (new MiniConfig).toInstance val chirrtl = firrtl.Parser.parse(chisel3.Driver.emit(() => new Tile(params))) val writer = new FileWriter(new File(dir, s"${chirrtl.main}.fir")) writer write chirrtl.serialize writer.close
これはTileモジュールからはtileParams: Parameters
として見えるようになる。さらに省略してp
という文字で参照できるようにしてある。
class Tile(tileParams: Parameters) extends Module with TileBase { implicit val p = tileParams
これらのパラメータは暗黙的なパラメータとして各モジュールに伝搬される。
class HostIO(implicit p: Parameters) extends CoreBundle()(p) { val fromhost = Flipped(Valid(UInt(xlen.W))) val tohost = Output(UInt(xlen.W)) } class CoreIO(implicit p: Parameters) extends CoreBundle()(p) { val host = new HostIO val icache = Flipped((new CacheIO)) val dcache = Flipped((new CacheIO)) }
これを応用して、自作のDiplomacyジェネレータにParameterを追加してみた。以下のような新たなConfigを作成してみる。
import freechips.rocketchip.config._ class Bus128BitConfig extends Config((site, here, up) => { case BusWidthBytes => 128 / 8 })
そして、このConfigをインスタンス化して実装モジュールに適用させてみた。
object Generator { final def main(args: Array[String]) { val p = (new Bus64BitConfig).toInstance val verilog = Driver.emitVerilog( new TestHarness()(p) ) } }
Bus128BitConfig
内で定義したBusWidthBytes
は、以下のSoCモジュール内でパラメータとして使用している。p(BusWidthBytes)
として参照可能になっている。
class core_complex(txns: Int)(implicit p: Parameters) extends LazyModule { val pusher1 = LazyModule(new TLPatternPusher("pat1", Seq( new WritePattern(0x100, 0x2, 0x012345678L), new WritePattern(0x500, 0x2, 0x0abcdef01L), new ReadExpectPattern(0x100, 0x2, 0x012345678L), new ReadExpectPattern(0x500, 0x2, 0x0abcdef01L) ))) val ifu = LazyModule(new ifu("ifu")) val xbar = LazyModule(new TLXbar) // p(BusWidthBytes)でバスの幅を決めている val memory = LazyModule(new TLRAM(AddressSet(0x02000, 0x0fff), beatBytes = p(BusWidthBytes))) xbar.node := pusher1.node ...
これで生成したVerilogを確認してみると、無事にバス幅を128ビットに変更できていた。
module TLRAM( input clock, input reset, output auto_in_a_ready, input auto_in_a_valid, input [2:0] auto_in_a_bits_opcode, input [2:0] auto_in_a_bits_param, input [2:0] auto_in_a_bits_size, input auto_in_a_bits_source, input [13:0] auto_in_a_bits_address, input [15:0] auto_in_a_bits_mask, input [127:0] auto_in_a_bits_data, input auto_in_a_bits_corrupt, input auto_in_d_ready, output auto_in_d_valid, output [2:0] auto_in_d_bits_opcode, output [2:0] auto_in_d_bits_size, output auto_in_d_bits_source, output [127:0] auto_in_d_bits_data ); ....