Diplomacyを使ってOCPバスを作成するプロジェクト、次はクロスバーを作成してみよう。クロスバーは2つ以上のスレーブノードを接続してバスをスプリットする。
val ram0 = LazyModule(new OCPRAM(AddressSet(0x000, 0x3ff))) val ram1 = LazyModule(new OCPRAM(AddressSet(0x400, 0x3ff))) val xbar = LazyModule(new OCPXbar()) xbar.node := OCPDelayer(0.01) := pusher.node ram0.node := xbar.node ram1.node := xbar.node
上記のように、クロスバーの実装を代入演算子の左側に置くことでソースノードを指定する。一方でクロスバーの実装を代入演算子の右側に置くことでスレーブノードを指定している。このスレーブノードはいくつでも指定することができる。
この実装はどのように実現されているのだろうか?中身を見てみる。
chisel-hw/src/main/scala/ocp/Xbar.scala
class OCPXbar(policy: OCPArbiter.Policy = OCPArbiter.roundRobin)(implicit p: Parameters) extends LazyModule { val node = OCPNexusNode( clientFn = { seq => ... } managerFn = { seq => ... }
ノードの種類としてはネクサスノードを使用している。ネクサスノードの解説はこうだ。
https://chipyard.readthedocs.io/en/latest/TileLink-Diplomacy-Reference/NodeTypes.html#nexus-node
The nexus node is similar to the adapter node in that it has a different output interface than input interface. But it can also have a different number of inputs than it does outputs. This node type is mainly used by the
TLXbar
widget, which provides a TileLink crossbar generator. You will also likely not need to define this node type manually, but its invocation is as follows.
chisel-hw/src/main/scala/ocp/Nodes.scala
case class OCPNexusNode( clientFn: Seq[OCPClientPortParameters] => OCPClientPortParameters, managerFn: Seq[OCPManagerPortParameters] => OCPManagerPortParameters)( implicit valName: ValName) extends NexusNode(OCPImp)(clientFn, managerFn) with OCPFormatNode
ノードの接続を行うのは以下の実装だ。複数の入力を1つのベクトルに代入する。
chisel-hw/src/main/scala/ocp/Xbar.scala
for (i <- 0 until in.size) { val r = inputIdRanges(i) if (connectAIO(i).exists(x=>x)) { in(i).cmd <> io_in(i).cmd in(i).cmd.bits.tagId := io_in(i).cmd.bits.tagId | UInt(r.start) } else { in(i).cmd.mcmd := UInt(0) io_in(i).cmd.ready := Bool(true) } if (connectCIO(i).exists(x=>x)) { in(i).data <> io_in(i).data in(i).data.bits.tagId := io_in(i).data.bits.tagId | UInt(r.start) } else { in(i).data.valid := Bool(false) io_in(i).data.ready := Bool(true) } if (connectDIO(i).exists(x=>x)) { in(i).resp <> io_in(i).resp } else { in(i).resp.valid := Bool(false) io_in(i).resp.ready := Bool(true) } }
ノードの出力先は以下のようにしている。
chisel-hw/src/main/scala/ocp/Xbar.scala
val out = Wire(Vec(io_out.size, OCPBundle(wide_bundle))) for (o <- 0 until out.size) { val r = outputIdRanges(o) if (connectAOI(o).exists(x=>x)) { io_out(o).cmd <> out(o).cmd } else { out(o).cmd.ready := Bool(true) io_out(o).cmd.mcmd := UInt(0) } if (connectCOI(o).exists(x=>x)) { io_out(o).data <> out(o).data } else { out(o).data.ready := Bool(true) io_out(o).data.valid := Bool(false) } if (connectDOI(o).exists(x=>x)) { out(o).resp <> io_out(o).resp out(o).resp.bits.tagId := io_out(o).resp.bits.tagId | UInt(r.start) } else { out(o).resp.valid := Bool(false) io_out(o).resp.ready := Bool(true) } }
OCPアービタの接続を行う。
chisel-hw/src/main/scala/ocp/Xbar.scala
// Arbitrate amongst the sources for (o <- 0 until out.size) { OCPArbiter(policy, out(o).cmd, filter(beatsCmdI zip portsCmdOI(o), connectAOI(o)):_*) OCPArbiter(policy, out(o).data, filter(beatsDataI zip portsDataOI(o), connectCOI(o)):_*) } for (i <- 0 until in.size) { OCPArbiter(policy, in(i).resp, filter(beatsRespO zip portsRespIO(i), connectDIO(i)):_*) }