ずーっとRoCCインタフェースの勉強をやっているのだが、やっとわかった気がする。途中で動作が止まってしまうのはやはり途中でリクエストキューがいっぱいになってしまうからだ。 RoCCインタフェースにはタグが付いているのだが、このタグにちゃんとしたIDを渡してやらなければならない。
RoCCインタフェース経由でCPUとDcacheが接続される仕組み
ザックリ書くと以下の図の通りなのだが、実際にはRoCCアクセラレータ部とCPUのDcacheの間に接続インタフェースがあり、これのフロー制御を行う必要がある。 簡単に説明するとここにリクエストキューが入っており、過剰なリクエストが発行されてもここでフロー制御が実行されるようになっている。ここのエントリ数はデフォルトで2だ。
(https://inst.eecs.berkeley.edu/~cs250/sp16/disc/Disc02.pdf より引用)
Chisel(Scala)では以下のように記述されている。これを波形を取って観測してみると、内部のreplayq
というリクエストを保持するためのキューの、Ready信号が途中で落ちてしまうため動作が停止してしまうようだった。
val replayq = Module(new SimpleHellaCacheIFReplayQueue(2)) val req_arb = Module(new Arbiter(new HellaCacheReq, 2)) val req_helper = DecoupledHelper( req_arb.io.in(1).ready, replayq.io.req.ready, io.requestor.req.valid) req_arb.io.in(0) <> replayq.io.replay req_arb.io.in(1).valid := req_helper.fire(req_arb.io.in(1).ready) req_arb.io.in(1).bits := io.requestor.req.bits io.requestor.req.ready := req_helper.fire(io.requestor.req.valid) replayq.io.req.valid := req_helper.fire(replayq.io.req.ready) replayq.io.req.bits := io.requestor.req.bits
DecoupledHelper の動作について
Scalaについてあまり知らないのでずいぶんと苦労したが、DecoupledHelper
という記述によりReadyとValidのフロー制御が実現されている。
DecoupledHelper
の定義は以下のようになっている。これだけでは何のことだか分からないが、どうもマッチする要素を除いてリストをAndでリダクションする機能を備えているようだ。
object DecoupledHelper { def apply(rvs: Bool*) = new DecoupledHelper(rvs) } class DecoupledHelper(val rvs: Seq[Bool]) { def fire(exclude: Bool, includes: Bool*) = { (rvs.filter(_ ne exclude) ++ includes).reduce(_ && _) } }
デフォルトでは、このHelperに対して3つの要素が登録されている。req_arb.io.in(1).ready, replayq.io.req.ready, io.requestor.req.valid
の3つだ。
ここで、io.requestor.req.ready
が立ち上がるためにはどうしたらよいかというと、fire()
の機能として、fire()
の引数に指定されている要素を「除いて」すべての信号が立ち上がっていればOKとする回路になる。
これ、そうだったらそのようにScalaで書けばよいのに。なんでこんな風にしているのかは不明。
(req_arb.io.in(1).ready, replayq.io.req.ready, io.requestor.req.valid) から io.requestor.req.valid を除去 --> (req_arb.io.in(1).ready, replayq.io.req.ready) が立ち上がっていればよい。
SimpleHellaCacheIFReplayQueue の制御
SimpleHellaCacheIFReplayQueue
は以下で定義されており、Req.Valid
, Req.Ready
でキューへの代入、Resp.Valid
で開放する。
val nackq = Module(new Queue(UInt(width = log2Up(depth)), depth)) val replaying = Reg(init = Bool(false)) val next_inflight_onehot = PriorityEncoderOH(~inflight) val next_inflight = OHToUInt(next_inflight_onehot) val next_replay = nackq.io.deq.bits val next_replay_onehot = UIntToOH(next_replay) val next_replay_req = reqs(next_replay) // Keep sending the head of the nack queue until it succeeds io.replay.valid := nackq.io.deq.valid && !replaying io.replay.bits := next_replay_req // Don't allow new requests if there is are replays waiting // or something being nacked. io.req.ready := !inflight.andR && !nackq.io.deq.valid && !io.nack.valid
このときに、解放するTagのIDが一致しているかを見ている部分がある。リクエスト元から出すタグ情報が間違っているせいで、前回までの回路は正しく動作しなかった。
// Match on the tags to determine the index of nacks or responses val nack_onehot = Cat(reqs.map(_.tag === io.nack.bits).reverse) & inflight val resp_onehot = Cat(reqs.map(_.tag === io.resp.bits.tag).reverse) & inflight
Tagはデフォルトで9ビット用意されている。これをとりあえずリクエスト毎にシーケンシャルに使うように変更すると、とりあえずシンプルな形ではあるが、自作アクセラレータのインタフェースが動き始めた。