SiFive Inclusive Cacheは複数コアからのリクエストを処理しコヒーレントを取ることのできるL2キャッシュであるが、その内部構成を見ていこうと思う。
GTKWaveで見たところ、ざっくりと以下のような階層構成になっていた。
InclusiveCacheのインタフェースとしては、TileLinkのマスタとスレーブが一対ずつ定義されていた。
module InclusiveCache( // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127022:2] input clock, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127023:4] input reset, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127024:4] output auto_ctl_in_a_ready, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127025:4] input auto_ctl_in_a_valid, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127025:4] input [2:0] auto_ctl_in_a_bits_opcode, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127025:4] input [2:0] auto_ctl_in_a_bits_param, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127025:4] input [1:0] auto_ctl_in_a_bits_size, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127025:4] input [8:0] auto_ctl_in_a_bits_source, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127025:4] input [25:0] auto_ctl_in_a_bits_address, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127025:4] input [7:0] auto_ctl_in_a_bits_mask, // @[chipyard.TestHarness.DualMediumBoomConfig.fir 127025:4] ...
実装としてはmods
の数だけInclusiveCacheがインスタンス化されるようで、オプションを指定すればmods
の数だけL2をバンク分けすることができそうだ。
// Create the L2 Banks val mods = (node.in zip node.out) map { case ((in, edgeIn), (out, edgeOut)) => edgeOut.manager.managers.foreach { m => require (m.supportsAcquireB.contains(xfer), s"All managers behind the L2 must support acquireB($xfer) " + s"but ${m.name} only supports (${m.supportsAcquireB})!") val params = InclusiveCacheParameters(cache, micro, control.isDefined, edgeIn, edgeOut) val scheduler = Module(new Scheduler(params))
このなかのSchedulerというのがキモのモジュールらしい。
まずはインタフェースから、TileLinkの5つのチャネルがそれぞれUp側とDown側で用意されている。
class Scheduler(params: InclusiveCacheParameters) extends Module { val io = new Bundle { val in = TLBundle(params.inner.bundle).flip val out = TLBundle(params.outer.bundle) // Way permissions val ways = Vec(params.allClients, UInt(width = params.cache.ways)).flip val divs = Vec(params.allClients, UInt(width = InclusiveCacheParameters.lfsrBits + 1)).flip // Control port val req = Decoupled(new SinkXRequest(params)).flip val resp = Decoupled(new SourceXRequest(params)) } val sourceA = Module(new SourceA(params)) val sourceB = Module(new SourceB(params)) val sourceC = Module(new SourceC(params)) val sourceD = Module(new SourceD(params)) val sourceE = Module(new SourceE(params)) val sourceX = Module(new SourceX(params)) io.out.a <> sourceA.io.a io.out.c <> sourceC.io.c io.out.e <> sourceE.io.e io.in.b <> sourceB.io.b io.in.d <> sourceD.io.d io.resp <> sourceX.io.x val sinkA = Module(new SinkA(params)) val sinkC = Module(new SinkC(params)) val sinkD = Module(new SinkD(params)) val sinkE = Module(new SinkE(params)) val sinkX = Module(new SinkX(params)) sinkA.io.a <> io.in.a sinkC.io.c <> io.in.c sinkE.io.e <> io.in.e sinkD.io.d <> io.out.d sinkX.io.x <> io.req
主要なモジュールとしては以下のような感じかな。ディレクトリ、DRAM、リクエストコントローラ、MSHRランダム発生器となっているようだ。
val directory = Module(new Directory(params)) val bankedStore = Module(new BankedStore(params)) val requests = Module(new ListBuffer(ListBufferParameters(new QueuedRequest(params), 3*params.mshrs, params.secondary, false))) val mshrs = Seq.fill(params.mshrs) { Module(new MSHR(params)) }
requests
は実体はリクエストキューになっており、単なるFIFOとなっているようだ。
class QueuedRequest(params: InclusiveCacheParameters) extends InclusiveCacheBundle(params) { val prio = Vec(3, Bool()) // A=001, B=010, C=100 val control= Bool() // control command val opcode = UInt(width = 3) val param = UInt(width = 3) val size = UInt(width = params.inner.bundle.sizeBits) val source = UInt(width = params.inner.bundle.sourceBits) val tag = UInt(width = params.tagBits) val offset = UInt(width = params.offsetBits) val put = UInt(width = params.putBits) }