FPGA開発日記

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

オープンソース・アウトオブオーダCPU NaxRiscvを概観する (PLRUのソースコードを概観する3)

ChatGPTにSpinal-HDLのstageableについて聞いてみた。

stageableは、パイプラインの各ステージ間で扱うデータや信号を定義するクラスです。これにより、複数のステージで使用されるデータを統一的に扱うことができ、ステージ間でのデータ転送や遅延の処理を抽象化します。 主な特徴: パイプラインステージ間でのデータ移動の簡便化: パイプラインの各ステージで使用するシグナルをstageableとして定義しておくと、各ステージ間でデータを自動的に転送したり、遅延させたりする操作が簡単に行えます。これにより、手動でレジスタを使って信号を保持する必要がなくなります。 信号の名前とタイプの一元管理: stageableを使うことで、各信号の型や名前を明確に定義でき、複数のステージで同じ定義を使うことが可能です。これにより、コードの再利用性が高まり、設計の整合性が向上します。 パイプライン処理の抽象化: 各ステージ間で行われるデータの処理(例えば、特定の条件でデータを次のステージに進めるかどうかなど)を抽象的に記述することができ、コードがより読みやすくなります。

    val PLRU = Stageable(Plru.State(wayCount))
    val plru = new Area {
      val ram = Mem.fill(linePerWay)(Plru.State(wayCount))
      val write = ram.writePort
    }

つまり、まずPlru.State(wayCount)をパイプラインステージ用に定義している。Plru.Stateの定義は以下のようになっている。 データ構造としては、entries = WayCount = 4なので2要素のVecが定義されており、1要素目のサイズが1-bit, 2要素目のサイズが2-bitとなっている。 これをパイプライン構造で持つことになっている。

object Plru{
  def State(entries : Int) = Vec.tabulate(log2Up(entries))(l =>  Bits(1 << l bits))
}
        val plruLogic = new Area {
          val core = new Plru(wayCount, false)
          core.io.context.state := PLRU
          core.io.update.id := OHToUInt(WAYS_HITS)

          plru.write.valid := False
          plru.write.address := FETCH_PC(lineRange)
          plru.write.data := core.io.update.state

          refill.start.wayToAllocate := core.io.evict.id
//          refill.start.wayToAllocate := refill.randomWay                                                                                                                                                                                                                  
        }

evictは、現在のコンテキストに応じてEviction候補を静的に出力するようになってると思うのだけれども:

  val evict = new Area{
    val sel = Vec.fill(log2Up(entries))(Bool())    val logic = for(i <- 0 until log2Up(entries)) yield new Area{
      val stateSel = U(B(sel.take(i).reverse))
      val state = io.context.state(i)(stateSel)
      sel(i) := !state

      val validCheck = withEntriesValid generate new Area{
        val groups = io.context.valids.subdivideIn(1 << i slices).map(e => Vec(e.subdivideIn(2 slices).map(!_.orR)))
        val notOks = groups.read(stateSel)
        sel(i) clearWhen(notOks(1)) setWhen(notOks(0))
      }
    }
    io.evict.id := U(sel.reverse.asBits)
  }

context.stateにはPLRUの現在のstateが挿入されており、例えば、state(WayCount=4)であったとすると、1エントリ目は1ビット、2エントリ目は2ビットのstateが入っているので、 1エントリ目は0ビットの選択(つまりstate[0][-1:0]となり、0がが問答無用で指定される)され、そのビット反転が最初のインデックスビットになる。 続いて、2エントリ目は1ビットの選択(つまりstate[1][se[0:0]]が指定される)され、そのビット反転が最初のインデックスビットとなる。

そうすると、最初のコンテキストが 0 00 とすると、Eviction候補は1 10となる。

updateはWayのヒットしたビットに基づいて次に指すポインタを決める機能のようだ。 update.idはヒットしたWayのインデックス番号が含まれており、最上位からビットを切り出して設定する。

  val update = new Area{
    val logic = for(i <- 0 until log2Up(entries)) yield new Area{
      val state = io.update.state(i)
      val sel = io.update.id.takeHigh(i).asUInt
      if(i != 0) state := io.context.state(i)
      state(sel) := io.update.id(log2Up(entries)-i-1)
    }
  }