FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RoCCを使ったRocket Core拡張方法の調査 (6. 波形デバッグ2)

ずーっとRoCCインタフェースの勉強をやっているのだが、やっとわかった気がする。途中で動作が止まってしまうのはやはり途中でリクエストキューがいっぱいになってしまうからだ。 RoCCインタフェースにはタグが付いているのだが、このタグにちゃんとしたIDを渡してやらなければならない。

RoCCインタフェース経由でCPUとDcacheが接続される仕組み

ザックリ書くと以下の図の通りなのだが、実際にはRoCCアクセラレータ部とCPUのDcacheの間に接続インタフェースがあり、これのフロー制御を行う必要がある。 簡単に説明するとここにリクエストキューが入っており、過剰なリクエストが発行されてもここでフロー制御が実行されるようになっている。ここのエントリ数はデフォルトで2だ。

f:id:msyksphinz:20170825020600p:plain (https://inst.eecs.berkeley.edu/~cs250/sp16/disc/Disc02.pdf より引用)

Chisel(Scala)では以下のように記述されている。これを波形を取って観測してみると、内部のreplayqというリクエストを保持するためのキューの、Ready信号が途中で落ちてしまうため動作が停止してしまうようだった。

  • rocket-chip/src/main/scala/rocket/SimpleHellaCacheIF.scala
  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

f:id:msyksphinz:20170905021202p:plain

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で開放する。

  • rocket-chip/src/main/scala/rocket/SimpleHellaCacheIF.scala
  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ビット用意されている。これをとりあえずリクエスト毎にシーケンシャルに使うように変更すると、とりあえずシンプルな形ではあるが、自作アクセラレータのインタフェースが動き始めた。

f:id:msyksphinz:20170905023544p:plain