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) } }