FPGA開発日記

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

TileLinkを使った自作CPU+多彩なバス接続を考える

ちょっと自作CPUしてみる

  • Chiselを使って自作CPUして、それにTileLinkを接続して簡単なマルチコアシステムを作ってみる。

作ったCPUコア: RV64の簡単な5段パイプライン

まともに検証していないけどとりあえずriscv-tests程度は動くCPUコアを作った。

github.com

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

  • フェッチバス・データバスは非常に単純なもの(非TileLink / 非AXIバス)

github.com

github.com

自作CPUをどのようにしてTileLinkに組み込んでSoCを構築するか?

  1. 自作CPUをLazyModuleでラップする
  2. TileLinkのノードを定義する
  3. TileLinkのノードと自作CPUのノードを接続する
f:id:msyksphinz:20201213003152p:plain
図. 自作CPUのインタフェース構成
  • TileLinkのノードは2つ用意している。
    • 命令フェッチ用
    • データアクセス用
  • どちらもTileLinkだが、自作CPUは独自バスプロトコルなので無理やり置き換えている。
    • これで、CoreTopモジュールはTileLinkのインタフェースを2つ持ったモジュールに置き換わる
 // TileLinkで定義した命令・データバスのClientNodeを内部に引っ張ってくる
    val (inst_out, inst_edge) = inst_node.out(0)
    val (data_out, data_edge) = data_node.out(0)
    val baseEnd = 0
    val (sizeEnd, sizeOff) = (inst_edge.bundle.sizeBits + baseEnd, baseEnd)
    val (sourceEnd, sourceOff) = (inst_edge.bundle.sourceBits + sizeEnd, sizeEnd)
    val beatBytes = inst_edge.bundle.dataBits

    // 自作CPUのインスタンス定義
    val cpu = Module(new Cpu(conf, hartid))

    // TileLinkのフェッチポートの信号と、自作CPUのフェッチポート(独自プロトコル)を接続
    inst_out.a.valid := cpu.io.inst_bus.req
    inst_out.a.bits.address := cpu.io.inst_bus.addr
    inst_out.a.bits.opcode := TLMessages.Get
    ...
    cpu.io.inst_bus.ready := inst_out.a.ready
    cpu.io.inst_bus.ack := inst_out.d.valid
    cpu.io.inst_bus.rddata := inst_out.d.bits.data.asSInt

    // TileLinkのデータアクセスポートの信号と、自作CPUのデータアクセスポート(独自プロトコル)を接続
    data_out.a.valid := cpu.io.data_bus.req
    data_out.a.bits.address := cpu.io.data_bus.addr
    data_out.a.bits.opcode := TLMessages.Get
    ...
    cpu.io.data_bus.ack := data_out.d.valid
    cpu.io.data_bus.rddata := data_out.d.bits.data.asSInt

TileLinkモジュールを持っCoreTopを使ってSoCを作る

CoreTopとメモリモジュールを持つCoreComplexモジュールを作成してこれらを接続する。

class core_complex[Conf <: RVConfig] 
  (conf: Conf, numCores: Int, ramBeatBytes: Int, txns: Int)(implicit p: Parameters) 
  extends LazyModule {
  // 外部からメモリにデータアクセスするためのデータローダ
  val loader = LazyModule(new loader("loader"))

  // CPUコア(numCore分だけ複数個インスタンス化する)
  val core   = Seq.tabulate(numCores) { case i => LazyModule(new CoreTop(conf, i, "core" + i.toString)) }
  // TileLinkのクロスバ
  val xbar   = LazyModule(new TLXbar)
  // TileLinkインタフェースを持つメモリ
  val memory = LazyModule(new TLRAM(AddressSet(0x80000000L, 0x0ffff), beatBytes = ramBeatBytes))

  // クロスバにデータローダを接続する
  xbar.node := loader.node
  core.foreach { case (core) => {
    // クラスバにCPUコアの命令フェッチポートとデータポートを接続する
    xbar.node := TLDelayer(0.5) := core.inst_node
    xbar.node := TLDelayer(0.5) := core.data_node
  }
  }
  // クロスバをメモリに接続する
  memory.node := xbar.node
f:id:msyksphinz:20201213003216p:plain
図. 自作CPUをTileLinkを通じてメモリに接続し、簡単なSoCを構成する

numCoresを>1にして簡単にマルチコア構成を実現

上記のChiselの記述ならば、簡単にマルチコアにできる。nCoresに2, 3, ... を設定するだけで自動的にクロスバに複数のCPUが接続されるようになる。

f:id:msyksphinz:20201213003241p:plain
図. 自作CPUをTileLinkを通じてメモリに接続する。コア数=4の場合