1

在 LazyModule.scala 中,函数 AutoBundle() 用 flipped = true 翻转 dangleIn 中的 Data(bundleIn) 以生成 autoIO,而在 Nodes.scala 中,类 sourceNode 中的函数 makeIOs() 翻转 bundleOut 以生成 IO,为什么它们不同?

LazyModule.scala 中的 AutoBundle() 代码:

/** [[AutoBundle]] will construct the [[Bundle]]s for a [[LazyModule]] in [[LazyModuleImpLike.instantiate]],
  *
  * @param elts is a sequence of data containing for each IO port  a tuple of (name, data, flipped), where
  *             name: IO name
  *             data: actual data for connection.
  *             flipped: flip or not in [[makeElements]]
  */
final class AutoBundle(elts: (String, Data, Boolean)*) extends Record {
  // We need to preserve the order of elts, despite grouping by name to disambiguate things.
  val elements: ListMap[String, Data] = ListMap() ++ elts.zipWithIndex.map(makeElements).groupBy(_._1).values.flatMap {
    // If name is unique, it will return a Seq[index -> (name -> data)].
    case Seq((key, element, i)) => Seq(i -> (key -> element))
    // If name is not unique, name will append with j, and return `Seq[index -> (s"${name}_${j}" -> data)]`.
    case seq => seq.zipWithIndex.map { case ((key, element, i), j) => i -> (key + "_" + j -> element) }
  }.toList.sortBy(_._1).map(_._2)
  require(elements.size == elts.size)

  // Trim final "(_[0-9]+)*$" in the name, flip data with flipped.
  private def makeElements(tuple: ((String, Data, Boolean), Int)) = {
    val ((key, data, flip), i) = tuple
    // Trim trailing _0_1_2 stuff so that when we append _# we don't create collisions.
    val regex = new Regex("(_[0-9]+)*$")
    val element = if (flip) data.cloneType.flip() else data.cloneType
    (regex.replaceAllIn(key, ""), element, i)
  }

  override def cloneType: this.type = new AutoBundle(elts: _*).asInstanceOf[this.type]
}

Nodes.scala 中的 makeIOs() 代码:

/** A node which represents a node in the graph which only has outward edges and no inward edges.
  *
  * A [[SourceNode]] cannot appear left of a `:=`, `:*=`, `:=*, or `:*=*`
  * There are no Mixed [[SourceNode]]s, There are no "Mixed" [[SourceNode]]s because each one only has an outward side.
  */
class SourceNode[D, U, EO, EI, B <: Data](imp: NodeImp[D, U, EO, EI, B])(po: Seq[D])(implicit valName: ValName)
  extends MixedNode(imp, imp)
{
  override def description = "source"
  protected[diplomacy] def resolveStar(iKnown: Int, oKnown: Int, iStars: Int, oStars: Int): (Int, Int) = {
    def resolveStarInfo: String =
      s"""$context
         |$bindingInfo
         |number of known := bindings to inward nodes: $iKnown
         |number of known := bindings to outward nodes: $oKnown
         |number of binding queries from inward nodes: $iStars
         |number of binding queries from outward nodes: $oStars
         |${po.size} outward parameters: [${po.map(_.toString).mkString(",")}]
         |""".stripMargin
    require(oStars <= 1,
      s"""Diplomacy has detected a problem with your graph:
         |The following node appears right of a :=* $oStars times; at most once is allowed.
         |$resolveStarInfo
         |""".stripMargin)
    require(iStars == 0,
      s"""Diplomacy has detected a problem with your graph:
         |The following node cannot appear left of a :*=
         |$resolveStarInfo
         |""".stripMargin)
    require(iKnown == 0,
      s"""Diplomacy has detected a problem with your graph:
         |The following node cannot appear left of a :=
         |$resolveStarInfo
         |""".stripMargin)
    if (oStars == 0)
      require(po.size == oKnown,
        s"""Diplomacy has detected a problem with your graph:
           |The following node has $oKnown outward bindings connected to it, but ${po.size} sources were specified to the node constructor.
           |Either the number of outward := bindings should be exactly equal to the number of sources, or connect this node on the right-hand side of a :=*
           |$resolveStarInfo
           |""".stripMargin)
    else
      require(po.size >= oKnown,
        s"""Diplomacy has detected a problem with your graph:
           |The following node has $oKnown outward bindings connected to it, but ${po.size} sources were specified to the node constructor.
           |To resolve :=*, size of outward parameters can not be less than bindings.
           |$resolveStarInfo
           |""".stripMargin
      )
    (0, po.size - oKnown)
  }
  protected[diplomacy] def mapParamsD(n: Int, p: Seq[D]): Seq[D] = po
  protected[diplomacy] def mapParamsU(n: Int, p: Seq[U]): Seq[U] = Seq()

  def makeIOs()(implicit valName: ValName): HeterogeneousBag[B] = {
    val bundles = this.out.map(_._1)
    val ios = IO(Flipped(new HeterogeneousBag(bundles.map(_.cloneType))))
    ios.suggestName(valName.name)
    bundles.zip(ios).foreach { case (bundle, io) => bundle <> io }
    ios
  }
}
4

2 回答 2

2

这是一段相当复杂的代码(外交/LazyModule 是一个非常先进的生成器),但.flip它是 Chisel 3 中的 Chisel 2 API 等效项Flipped(...)。它通过 Chisel 2 兼容层用于火箭芯片(import Chisel._相反到import chisel3._)。

Flipped反转类型的方向。请注意,这是递归的,并且类型可以是双向的(通常在Flipped使用时)。

例如:

class Example extends MultiIOModule {
  // .valid and .bits are inputs, .ready is an output
  val in = IO(Flipped(Decoupled(UInt(8.W))))
  // .valid and .bits are outputs, .ready is an input
  val out = IO(Decoupled(UInt(8.W)))
  
  out <> in
}

(Scastie 链接:https ://scastie.scala-lang.org/F91trxakSFSrlVrzk3VxcA )

基本上,当使用现成有效的接口时,您经常需要“翻转”生产者或消费者接口中的方向。

于 2021-04-06T20:23:43.677 回答
0

如果我能回答这个问题,让我试试:

  1. AutoBudle 部分

对于 AutoBundle 部分,值得注意的是eltsAutoBundle 类构造函数使用的实际上是来自Dangles外交框架生成的。特定节点的已解析 Dangles 实际上是danglesOut ++ danglesIn. 根据评论, danglesOut 实际上是 Seq of which describe the connections from this node output to other nodes inputs.dangles ,而 danglesIn 表示 Seq of dangles which describe the connections from this node input from other nodes outputs.。简而言之,这意味着 danglesOut 是输出端口的“悬挂抽象”,而 danglesIn 表示输入端口。另外,请注意 DanglesIn Seq 中的 items 的flipped字段为 true,而 DanglesOut 中的 items 的字段为 false。flipped这就是Dangles中 field 的语义。它强烈暗示flipped在外交背景下意味着“输入”,而“非翻转”意味着“输出”。

现在,让我们回到 AutoBundle 部分。它使用悬挂数据来创建凿子数据类型。然后用这个Data类型为对应的LazyModuleImp构造IO,代码如下:

val element = if (flip) data.cloneType.flip() else data.cloneType

这基本上说如果flipped里面的字段Dangle是真的,那么在构造 IO 时附加到这个 Data 的方向应该是某种形式的 "Input"。好吧,现在您实际上需要深入研究 chisel 库中的cloneType,bindingdirectin热混乱:cloneType 由 chisel 库用于克隆/复制现有的 Chisel Data 对象。为什么?也许是因为函数式编程语言中的不变性优于可变性。通过创建一个精确的副本,您不必修改预先存在的对象,这可能支持线程安全之类的东西。所以,cloneType 只是凿子的克隆对象的方式。def cloneType: this.type位于根数据中,对于所有凿子数据子类型,您必须实现该 cloneType 事物来告诉用户他们如何克隆一个新对象。因此,cloneType 高度依赖于实现。许多内置数据子类型(Bits(因此 UInt、SInt、Bool)、Bundle、DecoupledIO 等)具有内置cloneType实现,但如果您创建从 扩展的新子类型Record,用户定义cloneType仍然是强制性的。然而,大多数子类型基本上都有类似(new CusType).asInstanceOf[this.type]. (Bikesheding:我不得不说这this.type东西很连贯,他们真的应该使用 typeclass 或 F-type 来实现克隆的东西。但这可能会破坏兼容性。)还有另一个问题,如果你通过Data代码库阅读,有字段像specifiedDirectiondirectionbindspecifiedDirection指用户指定的数据的方向。While表示结合了父约束(输入、输出和双向,如果数据类型是聚合)direction的实际解析方向。有点棘手,这意味着此数据类型是否已绑定到硬件构造函数。例如,如果您定义 UInt(32.W),它只是一个结构声明,尚未绑定到任何 harewire(他们称之为未绑定类型或 chiselType)。但是,如果使用,或包装它,它会将您刚刚声明的数据类型标记为有界类型(硬件类型),有多种形式的绑定(, , , , ,等)specifiedDirectionBindingWire()Reg()IO()PortBindingOpBindingMemoryPortBindingRegBindingWireBindingChildBinding

在澄清了上面的 cloneType 东西之后,让我们再次通过这个 loc:

val element = if (flip) data.cloneType.flip() else data.cloneType

之后data.cloneType,您可能想知道 cloneType 调用是否保留了directionbinding信息?就像我之前说的,这是一个非常依赖于实现的问题,您需要逐个检查。但是,大多数实现将只是一个 loc: (new CusType).asInstanceOf[this.type],考虑到绑定过程都发生在包装器调用中(IO(iodef)将返回有界类型,但它仍然与 iodef 完全相同),因此绑定信息将消失一般在调用 cloneType 之后。在方向方面,考虑下面的代码:

class TestBundle extends Record{
  val a = Input(UInt(8.W))
  val b =  Output(UInt(8.W))
  val elements =ListMap("a" -> a, "b" -> b)
  override def cloneType: this.type = new TestBundle().asInstanceOf[this.type]
}

class TestModule extends Module{
  val testBundle = new TestBundle
  val flippedTestBundle = Flipped(testBundle)
  val io = IO(flippedTestBundle)
  val cloneIO = io.cloneType
  io.a := io.b
}

io是chisel typeTestBundle的有界类型,direction信息已经全部解析(TestBundle->Bidirection, a->Output, b-> Input,注意a和b的解析方向是翻转的)。但是,当您调用 时io.cloneType,它将返回一个全新的 TestBundleL: override def cloneType: this.type = new TestBundle().asInstanceOf[this.type]。因此,很明显 和 的所有影响都val flippedTestBundle = Flipped(testBundle)val io = IO(flippedTestBundle)被删除。directioncloneIO 中剩下的唯一信息就是specifiedDirectiona(Input) 和 b(Output)。我在某处读到凿子开发人员不建议在 Bundle 上应用方向性,除非捆绑本身真的是双向的。现在,让我们回顾一下这个位置:val element = if (flip) data.cloneType.flip() else data.cloneType. 它指示是否输入端口(在外交框架中解析),只需翻转该端口中附加的数据方向即可。如果端口是输出,则保持原样。我认为这是相当直截了当的吧?另外值得注意的是,在 Chisel 2 兼容层中,用户指定的方向Flipped被解析为Input如果父方向未指定。(未指定-> 输出)我希望这可以回答您关于 AutoBundle 的问题。现在,让我们深入研究 makeIO

  1. makeIOs 首先,为什么要 makeIOs?注意只有SourceNodeSinkNodehave makeIOs,而其他节点类型没有。在外交基础设施内部,SourceNodeSinkNode是 DAG 图的两端。在某些情况下,您希望使用固定 IO 扩展此外交图(可能用于测试)。在外交图中,SourceNode只有输出,而SinkNode只有输入,但是如果要扩展图,则必须对图的Source部分进行输入,而对Sink部分进行输出。见下图:

网络扩展

因此,考虑到我已经阐明了上面的 cloneType 和 flip 是什么,您可能很清楚为什么flipAutoBundle 和 MakeIos 是相反的。

希望这个答案能让事情变得更清楚一些。

于 2021-05-14T08:14:43.470 回答