これまでDiplomacyを使ってOCPバスを作ってきたが、まだ改善したいところがある。まずは
- OCPバスなのにコマンドチャネルにValid信号がある。
- 各種コマンドをサポートしていきたい。
特にValid信号は大きな問題だ。OCPはCommand ⇔ Ready信号のハンドシェークなので、Valid信号は不要だ。これを何とかして削除したい。
DiplomacyのBundleがValid/Readyを持っている理由
ではそもそもなぜDiplomacyのバスはValid/Readyを使うようになっているのかというと、Bundleの基本がDecoupledIOになっているからだ。DecoupledIOはValid信号、Ready信号、データという構成になっており、これがTileLink / AMBAのベースになっている。
chisel-hw/rocketchip/src/main/scala/tilelink/Bundles.scala
class TLBundle(val params: TLBundleParameters) extends Record { ... private val optA = Some (Decoupled(new TLBundleA(params))) private val optB = params.hasBCE.option(Decoupled(new TLBundleB(params)).flip) private val optC = params.hasBCE.option(Decoupled(new TLBundleC(params))) private val optD = Some (Decoupled(new TLBundleD(params)).flip) private val optE = params.hasBCE.option(Decoupled(new TLBundleE(params))) def a: DecoupledIO[TLBundleA] = optA.getOrElse(Wire(Decoupled(new TLBundleA(params)))) def b: DecoupledIO[TLBundleB] = optB.getOrElse(Wire(Decoupled(new TLBundleB(params)))) def c: DecoupledIO[TLBundleC] = optC.getOrElse(Wire(Decoupled(new TLBundleC(params)))) def d: DecoupledIO[TLBundleD] = optD.getOrElse(Wire(Decoupled(new TLBundleD(params)))) def e: DecoupledIO[TLBundleE] = optE.getOrElse(Wire(Decoupled(new TLBundleE(params))))
上記の通り、TileLinkのバスはすべてDecoupledIO
がベースとなっている。従って、通信はすべてValid / Readyのハンド―シェークとなる。
rocketchip/src/main/scala/amba/axi4/Bundles.scala
class AXI4Bundle(params: AXI4BundleParameters) extends AXI4BundleBase(params) { val aw = Irrevocable(new AXI4BundleAW(params)) val w = Irrevocable(new AXI4BundleW (params)) val b = Irrevocable(new AXI4BundleB (params)).flip val ar = Irrevocable(new AXI4BundleAR(params)) val r = Irrevocable(new AXI4BundleR (params)).flip ...
Irrevocalble
はReadyValidIOをベースとしているので、これも結局はReady / Validのハンドシェークだ。
一方で、APBはベースとなるBundleを使わずに、それぞれのビットを分解して定義していた。
rocketchip/src/main/scala/amba/apb/Bundles.scala
// Signal directions are from the master's point-of-view class APBBundle(params: APBBundleParameters) extends APBBundleBase(params) { // Flow control signals from the master val psel = Bool(OUTPUT) val penable = Bool(OUTPUT) // Payload signals val pwrite = Bool(OUTPUT) val paddr = UInt(OUTPUT, width = params.addrBits) val pprot = UInt(OUTPUT, width = params.protBits) val pwdata = UInt(OUTPUT, width = params.dataBits) val pstrb = UInt(OUTPUT, width = params.dataBits/8) val pauser = if (params.userBits > 0) Some(UInt(OUTPUT, width = params.userBits)) else None val pready = Bool(INPUT) val pslverr = Bool(INPUT) val prdata = UInt(INPUT, width = params.dataBits) ...
そこで、OCPのBundleも新しく自分でベースのBundleを定義することにした。
chisel-hw/src/main/scala/ocp/Bundles.scala
class OCPBundle(val params: OCPBundleParameters) extends Record { // Emulate a Bundle with elements abcde or ad depending on params.hasBCE private val optCmd = Some (CmdReady(new OCPBundleCmd (params))) private val optData = Some (Decoupled(new OCPBundleData(params))) private val optResp = Some (Flipped(Decoupled(new OCPBundleResp(params)))) def cmd : CmdReadyIO [OCPBundleCmd ] = optCmd. getOrElse(Wire(CmdReady (new OCPBundleCmd(params)))) def data: DecoupledIO[OCPBundleData] = optData.getOrElse(Wire(Decoupled(new OCPBundleData(params)))) def resp: DecoupledIO[OCPBundleResp] = optResp.getOrElse(Wire(Decoupled(new OCPBundleResp(params)))) ...
Data
チャネルとResp
チャネルはそのままDecoupledIO
を使用しているが、Command
チャネルはCmdReadyIO
という新しいIOバンドルを作成した。
chisel-hw/src/main/scala/ocp/CmdReadyIO.scala
class CmdReadyIO[+T <: Data](gen: T) extends Bundle { ... val ready = Input(Bool()) val mcmd = Output(UInt(3.W)) val bits = Output(genType) } object CmdReadyIO { implicit class AddMethodsToReadyValid[T<:Data](target: CmdReadyIO[T]) { /** Indicates if IO is both ready and valid */ def fire(): Bool = target.ready && (target.mcmd =/= 0.U(3.W)) } }
上記のように、コマンドを示すOuput(UInt(3.W))
と、Ready
信号、そしてデータを示すbits
を定義した。
さて、これだけでは終わらない。次にOCPバッファの改造をおこなう。OCPバッファにはBufferParams
というパラメータが使われているが、実はこのパラメータの内部にDecoupledIOを使用するQueueの実装が隠れている。
rocketchip/src/main/scala/diplomacy/Parameters.scala
case class BufferParams(depth: Int, flow: Boolean, pipe: Boolean) { require (depth >= 0, "Buffer depth must be >= 0") def isDefined = depth > 0 def latency = if (isDefined && !flow) 1 else 0 def apply[T <: Data](x: DecoupledIO[T]) = if (isDefined) Queue(x, depth, flow=flow, pipe=pipe) else x ...
上記のQueue
の実装はDecoupledIOで実装されている。これではちょっと厳しいので、ついでにQueueも改造してCmdQueue
を定義してしまおう。
chisel-hw/src/main/scala/ocp/CmdReadyIO.scala
case class BufferCmdParams(depth: Int, flow: Boolean, pipe: Boolean) { require (depth >= 0, "Buffer depth must be >= 0") def isDefined = depth > 0 def latency = if (isDefined && !flow) 1 else 0 def apply[T <: Data](x: CmdReadyIO[T]) = if (isDefined) CmdQueue(x, depth, flow=flow, pipe=pipe) else x override def toString() = "BufferCmdParams:%d%s%s".format(depth, if (flow) "F" else "", if (pipe) "P" else "") } object BufferCmdParams { implicit def apply(depth: Int): BufferCmdParams = BufferCmdParams(depth, false, false) val default = BufferCmdParams(2) val none = BufferCmdParams(0) val flow = BufferCmdParams(1, true, false) val pipe = BufferCmdParams(1, false, true) }
CmdQueue
は通常のキューを、インタフェースをCmdReadyIOに変えただけである。
ここまでで、OCPの新しいBundleを使う準備が整った。