これまで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
class APBBundle(params: APBBundleParameters) extends APBBundleBase(params)
{
val psel = Bool(OUTPUT)
val penable = Bool(OUTPUT)
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
{
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]) {
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を使う準備が整った。