I see some examples in the RocketChip, but could not find info in the API reference
masterNode :=* tlOtherMastersNode
DisableMonitors { implicit p => tlSlaveXbar.node :*= slaveNode }
I see some examples in the RocketChip, but could not find info in the API reference
masterNode :=* tlOtherMastersNode
DisableMonitors { implicit p => tlSlaveXbar.node :*= slaveNode }
These are not Chisel operators. Instead, they're defined and used by Rocket Chip's diplomacy
package. These are shorthand operators for doing different types of binding between diplomatic nodes.
No published API documentation for this exists, but you can start poking around in the diplomacy
package. The releveant location where these are defined is src/main/scala/diplomacy/Nodes.scala
.
The pull request comment on this API is very infomative.
Commonly, A := B
creates a pair of master and slave ports in A and B.
A :=* B
means the number of port pairs is decided by number of B := Other
,
and A :*= B
vice versa.
The most counterintuitive part is that the duplication of links is not achieved by duplication of the intermediate module, but the intermediate module's expanding of port list.
I have written a simple example to explore star connectors' behavior.
In the following code snippet, an TLIdentifyNode connects 3 TLClientNode using :=
,
and then it connects to a crossbar node using :=*
as master to crossbar.
Meanwhile, an TLIdentifyNode connects 2 TLManagerNode using :=
,
and then it connects to the same crossbar node using :*=
as salve to crossbar.
import chisel3._
import chisel3.core.dontTouch
import freechips.rocketchip.config._
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.tilelink._
class ClientConnector(implicit p: Parameters) extends LazyModule {
val node = TLIdentityNode()
override lazy val module = new LazyModuleImp(this) {
(node.in zip node.out) foreach { case ((bundleIn, edgeIn), (bundleOut, edgeOut)) =>
bundleOut <> bundleIn
}
}
}
class ManagerConnector(implicit p: Parameters) extends LazyModule {
val node = TLIdentityNode()
override lazy val module = new LazyModuleImp(this) {
(node.in zip node.out) foreach { case ((bundleIn, edgeIn), (bundleOut, edgeOut)) =>
bundleOut <> bundleIn
}
}
}
class Client(implicit p: Parameters) extends LazyModule {
val node = TLClientNode(
portParams = Seq(
TLClientPortParameters(Seq(
TLClientParameters("myclient1", IdRange(0, 1), supportsGet = TransferSizes(4), supportsProbe = TransferSizes(4))
))
)
)
override lazy val module = new LazyModuleImp(this) {
node.out.foreach { case(bundle, edge) =>
val (legal, a) = edge.Get(0.U, 0x1000.U, 2.U)
bundle.a.bits := a
bundle.a.valid := legal
bundle.d.ready := true.B
dontTouch(bundle)
}
}
}
class Manager(base: Int)(implicit p: Parameters) extends LazyModule {
val node = TLManagerNode(Seq(TLManagerPortParameters(Seq(TLManagerParameters(
address = Seq(AddressSet(base, 0xffff)),
supportsGet = TransferSizes(4)
)), beatBytes = 4)))
override lazy val module = new LazyModuleImp(this) {
node.in.foreach { case (bundle, edge) =>
when (bundle.a.fire()) {
val d = edge.AccessAck(bundle.a.bits, 0xdeadbeafL.U)
bundle.d.bits := d
bundle.d.valid := true.B
}
bundle.a.ready := true.B
}
}
}
class Playground(implicit p: Parameters) extends LazyModule {
val xbr = TLXbar()
val clientConnectors = LazyModule(new ClientConnector())
val managerConnectors = LazyModule(new ManagerConnector())
val clients = Seq.fill(3) { LazyModule(new Client()).node }
val managers = Seq.tabulate(2) { i: Int => LazyModule(new Manager(0x10000 * i)).node }
clients.foreach(clientConnectors.node := _)
managers.foreach(_ := managerConnectors.node)
managerConnectors.node :*= xbr
xbr :=* clientConnectors.node
override lazy val module = new LazyModuleImp(this) {
}
}
The corresponding verilog code of ManagerConnector
is (in brief):
module ManagerConnector(
`tilelink_bundle(auto_in_0),
`tilelink_bundle(auto_in_1),
`tilelink_bundle(auto_out_0),
`tilelink_bundle(auto_out_1)
);
// ...
endmodule
We can see diplomacy
framework is only responsible for parameter negotiation, port list generation and port connection. The duplication introduced by *
connection is guaranteed by the common code pattern:
(node.in zip node.out) foreach { ... }
In my opinion, this API simplifies connection between crossbar node and various nodes inside a specific module, and keeps the connection syntax consistent.
[Reference] A rocketchip reading note: https://github.com/cnrv/rocket-chip-read/blob/master/diplomacy/Nodes.md
It might be useful to read the documentation on diplomacy by lowrisc: https://www.lowrisc.org/docs/diplomacy/