4
votes

I see some examples in the RocketChip, but could not find info in the API reference

  masterNode :=* tlOtherMastersNode
  DisableMonitors { implicit p => tlSlaveXbar.node :*= slaveNode }
3

3 Answers

3
votes

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.

1
votes

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

0
votes

It might be useful to read the documentation on diplomacy by lowrisc: https://www.lowrisc.org/docs/diplomacy/