なんとなく内部構造も見えてきたので、ここらでRocket Chipにモジュールを追加する例題を追っていった際のメモ書き。 長くなりそうなので、適宜分割予定(Maxで5回、、かな)。最初にお断りしておきますが、まだ理解があやふやなのです。 ご承知おきください。
firesimのPWMモジュールの追加のサンプル
内容的にはfiresimのgithubリポジトリのREADMEに書いてある内容をトレースする。
同じ内容が以下にもドキュメント化されてまとまっていた。
こちらはこれまで自前でソースの解析を行ってきたParametes
/Configs
についても取り上げられているのでそちらも合わせて確認するといいと思う。
サンプルのPWMモジュール
例題として取り上げられているのは
となっている。
このチュートリアルを通して作成されるモジュールの完全なソースは以下に置いてあるので 躓いた場合はこちらをみればOKな親切仕様。
説明いわく"The easiest way to create a TileLink peripheral"らしいので、他にもやり方はある模様。
結果だけ先に書いておくと、以下の画像の様に今回追っていったPWMモジュールがRocket Chipに接続されることを確認できてます。
#そのままだと出来なかったのでそのへんは適宜修正してますが、5回目あたりでまとめて説明します。
作成するファイル
ということで早速説明を見ながら進めていくのだが、最初に何を作るのかを確認しておく。 一連の流れで準備することになるのは以下の4つのファイルだ。
ファイル名 | 役割 |
---|---|
Configs.scala | PWMモジュールの組み込みに必要となるConfigを実装するファイル(system.Configs.scalaに相当) |
PWM.scala | PWMの制御モジュール一式を実装 |
TestHarness.scala | Generatorと入力する最上位のモジュールを実装(system.TeseHarness.scalaに相当) |
Top.scala | PWMモジュールを組み込むシステムを実装(system.ExsampleRocketChip.scalaに相当) |
PWM.scala
最初は実際に接続されるPWMモジュールから。いい例題なので、ファイルを分割しつつ全部一通り見ていくことにする。
インポート
import chisel3._ import chisel3.util._ import freechips.rocketchip.subsystem.BaseSubsystem import freechips.rocketchip.config.{Parameters, Field} import freechips.rocketchip.diplomacy._ import freechips.rocketchip.regmapper.{HasRegMap, RegField} import freechips.rocketchip.tilelink._ import freechips.rocketchip.util.UIntIsOneOf
とりあえずRocket Chipのお作法に則ってモジュールを追加するには上記のパッケージが必要になる模様
PWM用のパラメータクラス(PWMBase)
PWMモジュールの内部で使用するパラメータを定義したケース・クラス。
case class PWMParams(address: BigInt, beatBytes: Int)
PWMのベースとなるクラス(PWMBase)
PWMのベースとなるクラス。役目としては実際のPWMの制御になりそう。これはなんの変哲も無いChiselのモジュールの実装なので特にこれ以上言うことも無い。
class PWMBase(w: Int) extends Module { val io = IO(new Bundle { val pwmout = Output(Bool()) val period = Input(UInt(w.W)) val duty = Input(UInt(w.W)) val enable = Input(Bool()) }) // The counter should count up until period is reached val counter = Reg(UInt(w.W)) when (counter >= (io.period - 1.U)) { counter := 0.U } .otherwise { counter := counter + 1.U } // If PWM is enabled, pwmout is high when counter < duty // If PWM is not enabled, it will always be low io.pwmout := io.enable && (counter < io.duty) }
TileLinkバスに接続するためのIOポート用トレイト(PWMTLBundle)
LazyModuleImp
にMix-inして使うためのトレイト
trait PWMTLBundle extends Bundle { val pwmout = Output(Bool()) }
TileLinkのルータに接続されるレジスタブロック(PWMTLModule)
ここでは以下の3つのレジスタが定義されている。
- period : PWMの周期
- duty : パルスのデュティ比
- enable : 有効化
この3つのレジスタの値が先に書いたPWMBase
に接続される形になる。
以下は冒頭に紹介したfirechipのソースをそのまま記載してます。
trait PWMTLModule extends HasRegMap { val io: PWMTLBundle implicit val p: Parameters def params: PWMParams val w = params.beatBytes * 8 require(w <= 32) // How many clock cycles in a PWM cycle? val period = Reg(UInt(w.W)) // For how many cycles should the clock be high? val duty = Reg(UInt(w.W)) // Is the PWM even running at all? val enable = RegInit(false.B) val base = Module(new PWMBase(w)) io.pwmout := base.io.pwmout base.io.period := period base.io.duty := duty base.io.enable := enable regmap( 0x00 -> Seq( RegField(w, period)), 0x04 -> Seq( RegField(w, duty)), 0x08 -> Seq( RegField(1, enable))) }
詳しい仕組みはまた追って解説したい(&今調査中)のだが、上記のようにregmap
に
レジスタアドレス: Int -> Seq(RegField())
の形でレジスタを格納しておくと、モジュール生成時に勝手にアクセスのための論理が 生成されるようになっているみたい。
PWMモジュール用のレジスタ・ルータ
こちらはRocket ChipのTileLinkバスに接続されるインターフェースになる部分のクラス定義。
TLRegisterRouter
を継承して、各種情報を入れておくとRocket Chipの実体化の際に解析が行われてデバイスツリーへの登録が行われる。
正直、文法的な部分がまだきちんと理解できていないのだが、この例の用のにMMIOとしてTileLinkに接続する場合においては、以下のソースの形がテンプレートになるようだ。
class PWMTL(c: PWMParams)(implicit p: Parameters) extends TLRegisterRouter( c.address, "pwm", Seq("ucbbar,pwm"), beatBytes = c.beatBytes)( new TLRegBundle(c, _) with PWMTLBundle)( new TLRegModule(c, _, _) with PWMTLModule)
PWMモジュールを追加用のトレイト(HasPeripheryPWM)
こちらはPWMモジュールをトレイト中で生成することで他のRocket Chipのモジュールと同様にMix-inによってモジュールを追加できるようにしている。
trait HasPeripheryPWM { this: BaseSubsystem => implicit val p: Parameters private val address = 0x2000 private val portName = "pwm" val pwm = LazyModule(new PWMTL( PWMParams(address, pbus.beatBytes))(p)) pbus.toVariableWidthSlave(Some(portName)) { pwm.node } }
PWMモジュールの実装実体となるModuleImpクラス(HasPeripheryPWMModuleImp)
こちらはモジュール生成時に実際のモジュールの境界になる。
そのため、こちらにPWMモジュールのIOポートの定義を行いouter
に入っているPWMモジュールのポートと接続を行う。
trait HasPeripheryPWMModuleImp extends LazyModuleImp { implicit val p: Parameters val outer: HasPeripheryPWM val pwmout = IO(Output(Bool())) pwmout := outer.pwm.module.io.pwmout }
と、ここまで見てきたようにMMIOのモジュールとして自前のモジュールをRocket ChipのTileLinkバス(pbus)に接続する場合には以下のモジュールが必要になるようだ。(括弧の中はここまでで紹介したPWMモジュールの対応する要素)
- ポート用のトレイト(PWMTLBundle)
- HasRegMapを継承したレジスタとレジスタマップを含むトレイト(PWMTLModule)
- TLRegisterRouterを継承したルータモジュール(TLRegisterRouter)
- Rocket Chip接続用のトレイト(HasPeripheryPWM)
- Rocket Chipの実装用トレイト(HasPeripheryPWMModuleImp)
次回はここで作成したPWMモジュールをRocket Chipのシステムに組み込むためのモジュール部分を確認していく。