diff --git a/src/GenUcieHeader.scala b/src/GenUcieHeader.scala index cba06f1..12c67d8 100644 --- a/src/GenUcieHeader.scala +++ b/src/GenUcieHeader.scala @@ -29,19 +29,21 @@ object GenUcieHeader { def render(): String = { val ports = uciePorts // The header holds one register layout, so every port has to agree on - // everything that layout depends on. Address and moduleId do not reach it: - // the offsets are relative to each port's own MMIO base, and the moduleId - // only names modules. + // everything that layout depends on. Address, moduleId and the clock + // distribution netlist do not reach it: the offsets are relative to each + // port's own MMIO base, the moduleId only names modules, and the netlist is + // physical design. val canonical = ports.map( _.copy( address = ports.head.address, - moduleId = ports.head.moduleId + moduleId = ports.head.moduleId, + clkDistLayout = ports.head.clkDistLayout ) ) require( canonical.forall(_ == canonical.head), "UCIe ports must share a register layout, but their parameters differ " + - s"beyond address and moduleId: ${canonical.distinct.mkString("\n")}" + s"beyond address, moduleId and clock distribution: ${canonical.distinct.mkString("\n")}" ) edu.berkeley.cs.uciedigital.tilelink.GenUcieHeader .render(ports.head, regenCommand) diff --git a/src/Iris.scala b/src/Iris.scala index e4eb9af..27bdefc 100644 --- a/src/Iris.scala +++ b/src/Iris.scala @@ -53,6 +53,7 @@ class IrisSystem(implicit p: Parameters) class IrisTop(implicit p: Parameters) extends LazyModule with BindingScope { val system = LazyModule(new IrisSystem) + UcieControl.connect(system) val clockGroupsSourceNode = ClockGroupSourceNode( Seq(ClockGroupSourceParameters()) ) @@ -487,10 +488,11 @@ class IrisConfig(sim: Boolean = false) routerParams = testchipip.soc.OffchipRouterParams(tableEntries = 4), ports = Seq( // PD hardens the two UCIe links together, so UcieComplexPort puts - // both instances inside one UcieComplex module. They are identical - // logic and share a `moduleId`, so they dedup into a single UcieTL - // underneath it; splitting them again is a matter of giving one its - // own `moduleId`. + // both instances inside one UcieComplex module. Each link is still + // its own module, `moduleId` 0 and 1, so that a physical design can + // give each the lane clock distribution network laid out for its + // own bump field (see `ClkDistLayout`). Each link also has its own + // high-speed and digital bypass clock bumps. UcieComplexPort(edu.berkeley.cs.uciedigital.tilelink.UcieTLParams( address = 0x200000, managerWhere = SBUS, @@ -501,7 +503,8 @@ class IrisConfig(sim: Boolean = false) address = 0x208000, managerWhere = SBUS, numLanes = 16, - includeDefaultModels = true + includeDefaultModels = true, + moduleId = 1 )) ))) ++ new WithIrisUncore(sim) diff --git a/src/TinyIris.scala b/src/TinyIris.scala index 94a0de6..ccc05ed 100644 --- a/src/TinyIris.scala +++ b/src/TinyIris.scala @@ -34,6 +34,7 @@ import sifive.blocks.inclusivecache.{InclusiveCachePortParameters} class TinyIrisTop(implicit p: Parameters) extends LazyModule with BindingScope { val system = LazyModule(new IrisSystem) + UcieControl.connect(system) val clockGroupsSourceNode = ClockGroupSourceNode( Seq(ClockGroupSourceParameters()) ) diff --git a/src/UcieComplex.scala b/src/UcieComplex.scala index 820782f..2a6cab9 100644 --- a/src/UcieComplex.scala +++ b/src/UcieComplex.scala @@ -2,8 +2,9 @@ package edu.berkeley.cs.iris import org.chipsalliance.cde.config.Parameters import freechips.rocketchip.diplomacy._ -import freechips.rocketchip.subsystem.TLBusWrapperLocation -import edu.berkeley.cs.uciedigital.tilelink.UcieTLParams +import freechips.rocketchip.subsystem.{BaseSubsystem, TLBusWrapperLocation} +import freechips.rocketchip.tilelink.{TLBuffer, TLFragmenter, TLWidthWidget} +import edu.berkeley.cs.uciedigital.tilelink.{UcieChipletLink, UcieTLParams} import testchipip.soc.{ ChipletLinkParams, ChipletLinkWrapper, @@ -81,3 +82,43 @@ object UciePort { .map(_.ports.collect { case UciePort(u) => u }) .getOrElse(Nil) } + +/** Attaches each UCIe link's register port, which the chiplet router can't. + * + * The router couples a port's control node to its control bus only when the + * port hands it a bare `TLRegisterNode`. A UCIe link's register port is a + * crossbar in front of its three register blocks -- PhyTest, controller and + * clocking -- so `UcieChipletLink` hands the router none and leaves the port + * to the top. This couples it to the router's control bus the way the router + * couples its own control nodes, with the fragmenter in front that the port + * needs. The router clocks the link, from a clock group the top drives off the + * same clock as the bus, so the port needs no crossing. + * + * Each top calls this on its system once the system is built. The router keeps + * its ports to itself, so the links are found by walking the system's + * children. + */ +object UcieControl { + def connect(system: BaseSubsystem)(implicit p: Parameters): Unit = + p(ChipletRoutingKey).foreach { params => + def links(lm: LazyModule): Seq[UcieChipletLink] = + lm.getChildren.reverse.flatMap { + case link: UcieChipletLink => Seq(link) + case child => links(child) + } + val ucieLinks = links(system) + require( + ucieLinks.size == UciePort.all(p).size, + s"found ${ucieLinks.size} UCIe links under the system, but the " + + s"chiplet router has ${UciePort.all(p).size} UCIe ports" + ) + + val cbus = system.locateTLBusWrapper(params.controlBusWhere) + ucieLinks.foreach { link => + cbus.coupleTo(s"${link.name}_control") { + link.regNode := TLWidthWidget(cbus.beatBytes) := TLBuffer() := + TLFragmenter(cbus.beatBytes, cbus.blockBytes) := TLBuffer() := _ + } + } + } +} diff --git a/ucie b/ucie index caf0daf..4db6449 160000 --- a/ucie +++ b/ucie @@ -1 +1 @@ -Subproject commit caf0daf1135f5b87225fa4903fcc3dfb87863de5 +Subproject commit 4db6449716976a79765828a6dc27547c6a0c6366