ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Rocket ChipのGeneratorのソースの解析メモ(7) - サンプルに沿ってPWMモジュールをTileLinkバスに追加してみる(1)

スポンサーリンク

なんとなく内部構造も見えてきたので、ここらで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回目あたりでまとめて説明します。

f:id:diningyo-kpuku-jougeki:20190909234138p:plain
Rocket Chipに追加されたPWMモジュール

作成するファイル

ということで早速説明を見ながら進めていくのだが、最初に何を作るのかを確認しておく。 一連の流れで準備することになるのは以下の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のシステムに組み込むためのモジュール部分を確認していく。