NaxRiscvのVerilogコードだが、やっぱり何が書いてあるのかさっぱりわからない。
とりあえずSpinalHDLのチュートリアルを読んで、基本的なところを確認していこうと思う。以下のページがよさそうだ。
抽象化 / HDL
NaxRiscvの実装では、SpinalHDL(Scalaのハードウェア記述ライブラリ)を使用する際に利用可能な多くのパラダイムを活用しています。
フレームワーク
NaxRiscvのトップレベルは、プラグインのリストをスケジューリングできるフレームワークを持つ空のコンポーネントであることがほとんどです。 フレームワーク自体はハードウェアを作成しません。
以下はNaxRiscvのトップレベルです:
class NaxRiscv(xlen : Int, plugins : Seq[Plugin]) extends Component{ NaxScope.create(xlen = xlen) //Will come back on that line later val framework = new Framework(plugins) }
設計がどの程度プラグイン間で分担されているかを概観するために、1つの機能CPUに対するプラグインのリストを以下に示します:
val plugins = ArrayBuffer[Plugin]() plugins += new DocPlugin() plugins += new MmuPlugin( spec = MmuSpec.sv32, ioRange = ioRange, fetchRange = fetchRange ) //FETCH plugins += new FetchPlugin() plugins += new PcPlugin(resetVector) plugins += new FetchCachePlugin( cacheSize = 4096*4, wayCount = 4, injectionAt = 2, fetchDataWidth = 64, memDataWidth = 64, reducedBankWidth = false, hitsWithTranslationWays = true, translationStorageParameter = MmuStorageParameter( levels = List( MmuStorageLevel( id = 0, ways = 4, depth = 32 ), MmuStorageLevel( id = 1, ways = 2, depth = 32 ) ), priority = 0 ), translationPortParameter = MmuPortParameter( readAt = 1, hitsAt = 1, ctrlAt = 1, rspAt = 1 ) ) plugins += new AlignerPlugin( decodeCount = 2, inputAt = 2 ) /* ... 以下略 ... */
これらのプラグインはそれぞれ、以下の選択肢がある:
- 他のプラグインが使用するサービスの実装(例:ジャンプインターフェイスの提供、リスケジューリングインターフェイスの提供、パイプラインスケルトンの提供)
- 他のプラグインの機能を使用する
- ハードウェアの作成
- 初期タスクの作成(プラグイン間のセットアップに使用)
- 後期タスクの作成(必要なハードウェアを作成するために一般的に使われる)
プラグインタスク
ここでは、2つのタスク(セットアップ/ロジック)を作成するダミープラグインのインスタンスを示します:
class DummyPlugin extends Plugin { val setup = create early new Area { // ここで他のプラグインをセットアップすることができる //このコードは常に、すべてのlate taskの前で実行される } val logic = create late new Area { //ここで(例えば)ハードウェアを生成することができる //このコードは常に、すべてのearly taskの後に開始される } }
create early
と create late
は、Frameworkクラスによってスケジューリングされた新しいスレッドでコードを実行することに注意してください。
service
の定義
例えば、他のプラグインにハードウェアジャンプインターフェースを提供するJumpService
は、次のように定義できます:
// エラボレーション時のソフトウェア・インタフェース trait JumpService extends Service{ def createJumpInterface(priority : Int) : Flow[JumpCmd] } // インタフェースのハードウェア・ペイロード case class JumpCmd(pcWidth : Int) extends Bundle{ val pc = UInt(pcWidth bits) }
service
の実装
先に示したJumpService
を例にとると、PcPlugin
は次のように実装できます:
case class JumpSpec(interface : Flow[JumpCmd], priority : Int) class PcPlugin() extends Plugin with JumpService{ val jumpsSpec = ArrayBuffer[JumpSpec]() override def createJumpInterface(priority : Int): Flow[JumpCmd] = { val spec = JumpSpec(Flow(JumpCmd(32)), priority) jumpsSpec += spec return spec.interface } val logic = create late new Area{ // ここで、PCロジックを実装し、jumpsSpecインターフェイスを管理する。 val pc = Reg(UInt(32 bits)) val sortedJumps = jumpsSpec.sortBy(_.priority) //Lower priority first for(jump <- sortedJumps){ when(jump.interface.valid){ pc := jump.interface.pc } } ... } }
サービスの使い方
別のプラグインは、以下のようにしてこのサービスを取得し、使用することができます:
class AnotherPlugin() extends Plugin { val setup = create early new Area { val jump = getService[JumpService].createJumpInterface(42) } val logic = create late new Area { setup.jump.valid := ??? setup.jump.pc := ??? } }
サービスパイプラインの定義
プラグインによっては、パイプラインのスケルトンを作成し、それを他のプラグインが入力することもできます。 例えば:
class FetchPlugin() extends Plugin with LockedImpl { val pipeline = create early new Pipeline{ val stagesCount = 2 val stages = Array.fill(stagesCount)(newStage()) import spinal.lib.pipeline.Connection._ //Connect every stage together for((m, s) <- (stages.dropRight(1), stages.tail).zipped){ connect(m, s)(M2S()) } } val logic = create late new Area{ lock.await() // 他のプラグインが、パイプラインの段階で望むものをすべて指定するまで、このブロッキングを行うことを許可する。 pipeline.build() } }
サービス・パイプラインの使用法
例えば、PcPluginはPC値をフェッチパイプラインに導入したいと思うだろう:
object PcPlugin extends AreaObject{ val FETCH_PC = Stageable(UInt(32 bits)) // FETCH_PC信号がパイプラインを通して使用可能であるという概念を定義する。 } class PcPlugin() extends Plugin with ...{ val setup = create early new Area{ getService[FetchPlugin].retain() // 関連するアクセスをすべて作成するまで、FetchPluginロジックのタスクを保持する必要がある。 } val logic = create late new Area{ val fetch = getService[FetchPlugin] val firstStage = fetch.pipeline.stages(0) firstStage(PcPlugin.FETCH_PC) := ??? // パイプラインのfirstStageでFETCH_PCの値を代入する。他のプラグインがダウンストリームでアクセスするかもしれない。 fetch.release() } }
実行ユニット
実行ユニットの実装は、このコンセプトのもう一つの実用的な使い方です。 一意の実行ユニット識別子を持つ新しいExecutionUnitBase
を作成することで、実行ユニットを生成することができます:
plugins += new ExecutionUnitBase("EU0")
次に、同じ識別子を持つ新しいExecutionUnitElementSimpleを追加することで、その実行ユニットを生成することができます:
plugins += new SrcPlugin("EU0") plugins += new IntAluPlugin("EU0") plugins += new ShiftPlugin("EU0")
以下は、以下の演算を操作するプラグインです:
- mul/div
- jump/branches
- load/store
- CSR accesses
- ebreak/ecall/mret/wfi
plugins += new ExecutionUnitBase("EU1", writebackCountMax = 1) plugins += new SrcPlugin("EU1") plugins += new MulPlugin("EU1", writebackAt = 2, staticLatency = false) plugins += new DivPlugin("EU1", writebackAt = 2) plugins += new BranchPlugin("EU1", writebackAt = 2, staticLatency = false) plugins += new LoadPlugin("EU1") plugins += new StorePlugin("EU1") plugins += new CsrAccessPlugin("EU1")( decodeAt = 0, readAt = 1, writeAt = 2, writebackAt = 2, staticLatency = false ) plugins += new EnvCallPlugin("EU1")(rescheduleAt = 2)
ShiftPlugin
以下は、ExecutionUnitElementSimpleプラグインの例としてのShiftPluginです:
object ShiftPlugin extends AreaObject { val SIGNED = Stageable(Bool()) val LEFT = Stageable(Bool()) } class ShiftPlugin(euId : String, staticLatency : Boolean = true, aluStage : Int = 0) extends ExecutionUnitElementSimple(euId, staticLatency) { import ShiftPlugin._ override def euWritebackAt = aluStage override val setup = create early new Setup{ import SrcKeys._ add(Rvi.SLL , List(SRC1.RF, SRC2.RF), DecodeList(LEFT -> True, SIGNED -> False)) add(Rvi.SRL , List(SRC1.RF, SRC2.RF), DecodeList(LEFT -> False, SIGNED -> False)) add(Rvi.SRA , List(SRC1.RF, SRC2.RF), DecodeList(LEFT -> False, SIGNED -> True)) add(Rvi.SLLI, List(SRC1.RF, SRC2.I ), DecodeList(LEFT -> True , SIGNED -> False)) add(Rvi.SRLI, List(SRC1.RF, SRC2.I ), DecodeList(LEFT -> False, SIGNED -> False)) add(Rvi.SRAI, List(SRC1.RF, SRC2.I ), DecodeList(LEFT -> False, SIGNED -> True)) } override val logic = create late new Logic{ val process = new ExecuteArea(aluStage) { import stage._ val ss = SrcStageables assert(Global.XLEN.get == 32) val amplitude = ss.SRC2(4 downto 0).asUInt val reversed = Mux[SInt](LEFT, ss.SRC1.reversed, ss.SRC1) val shifted = (S((SIGNED & ss.SRC1.msb) ## reversed) >> amplitude).resize(Global.XLEN bits) val patched = LEFT ? shifted.reversed | shifted wb.payload := B(patched) } } }