自作CPUの構成をマルチコアに変更したので、いくつか問題が表面化してしまっている。例えばメモリは1サイクルでアクセスできる仮定にしていたのを、いくつかValid & Readyの方式に実装し直さなければならない。このためには、とりあえずTileLinkにDelayerを挿入したうえで1コアに絞ってデバッグした方がなにかとやりやすい。という訳でコアの数をConfigurableにする。
まずはコア数はTestHarness
でnumCores
として宣言する。
class TestHarness()(implicit p: Parameters) extends Module { val rv_conf = new RV64IConfig val numCores = 2
これをcore_complex
にパラメータとして伝える。
val ldut = LazyModule(new core_complex(rv_conf, numCores = numCores, 4, 5000))
core_complex
ではCPUのインスタンスを以下のように書き換え、numCores
に基づいて複数のCPUをインスタンス化できるようにしている。
class core_complex[Conf <: RVConfig] (conf: Conf, numCores: Int, ramBeatBytes: Int, txns: Int)(implicit p: Parameters) extends LazyModule { val loader = LazyModule(new loader("loader")) val core = Seq.fill(numCores) { LazyModule(new CoreTop(conf, 0, "core")) }
バスの接続だが、オブジェクトに対するforeach
を用いてバスに対する接続を繰り返し記述するようにする。
xbar.node := loader.node core.foreach { case (core) => { xbar.node := TLDelayer(0.1) := core.inst_node xbar.node := TLDelayer(0.1) := core.data_node } } memory.node := xbar.node
デバッグポートだが、これもコアの数だけVec
の配列として宣言し、それぞれのコアに対して接続するようにした。
lazy val module = new LazyModuleImp(this) { val io = IO(new Bundle { val req = Input(Bool()) val addr = Input(UInt(32.W)) val data = Input(UInt(32.W)) val ready = Output(Bool()) val cpu_dbg = Output(Vec(numCores, new CpuDebugMonitor(conf))) })
// CPU Core Contorl val cpu_run = Seq.fill(numCores) { RegInit(false.B) } cpu_run.foreach { case(cpu_run) => cpu_run := Mux(io.req && io.req && (io.addr === 0x20000000.U), io.data(0), cpu_run) } core.zip(cpu_run).foreach { case (core, cpu_run) => core.module.io.run := cpu_run } io.cpu_dbg := core.map { case(core) => core.module.io.dbg_monitor }
これにより、コアの数を自由に変更することができるようになった。例えば、numCores=16
としてみる。
くそダサいが、一応こんなことができる。16コアの命令バスとデータバスを直接メモリに繋げるとこんなことができる、ということだ。
とりあえず1コアに戻してデバッグしよう。