Chisel开发Diplomacy框架


1. 简介

Diplomacy是一个参数协商框架,用于生成参数化的协议实现。在传统的IC设计中,如何更好地复用已有模块呢?如在Verilog中,在复用一个模块时,如果线宽不一致,需要手动修改模块的线宽,如果模块中内嵌多个模块时,每个关联模块的线宽都需要修改。如果修改不完全时,编译时就会出错。

Chisel作为一个更灵活的HDL,如何更好地解决这个问题呢?这就是Diplomacy提出的初衷。Diplomacy将模块的Port抽象为节点(Node),然后来进行协商,自动找到最优的线宽,以减少复用模块时需要修改的线宽代码。

2. 原理

2.1. 节点

一个模块有输入、输出以及当前端口,其分别抽象为节点(Node):

  • Source node:上级输入端口抽象的节点,也被称为驱动节点。
  • Nexus Node:当前端口抽象的节点。
  • Sink Node:输出到下个模块的端口抽象的节点,也称为监控节点。

Node的主要成员变量:

  • in, 输入的端口序列。
  • out, 输出的端口序列。
  • edges, 输入和输出线宽参数。

2.2. 参数

节点的参数就是线宽,线宽参数根据实际的模块端口需要,可能有1个或多个。一般使用case class来定义参数,增加可读性,另外也可以使用模式匹配来增强参数的表达能力。

  • UpwardParam,表示从上层模块传输过来的参数。
  • DownwardParam,表示传输到下层模块的参数。
  • EdgeParam,表示当前模块的参数。

2.3. 参数协商

参数协商有固定的规则,必须实现一个抽象接口SimpleNodeImp,其中包括向下参数、向上参数、当前参数以及实际参数类型这四个。

  • edge函数,完成参数的协商,并输出最终参数线宽。
  • bundle,输出实际的参数类型和线宽。
  • render,输出参数信息,用于查看分析,非关键项。

2.4. 电路代码

参数协商使用了LazyModule模式,只有在用到参数协商的时候,才执行,提升代码编译性能。与参数协商相关的电路代码更是在参数协商之后,所以Diplomacy提出一个LazyModuleImp模块,实际的电路需要在LazyModuleImp块内实现,这样就保证实际的电路在参数协商之后执行。

3. 示例

示例以一个加法器为例,两个驱动、一个加法器和校验器(监控器)。

3.1. 节点图

驱动、加法器和校验器的数据流程构成一个有向无环图,参数的流动不能是循环的,会导致计算循环而异常。

3.2. 参数

示例中参数只有一个,分为3种类型对就3种节点,可以写成下面:

case class UpwardParam(width: Int)
case class DownwardParam(width: Int)
case class EdgeParam(width: Int)

3.3. 参数协商

最后一个参数B就是实际参数的类型,线宽由上下参数协商得出。

// PARAMETER TYPES:                       D              U            E          B
object AdderNodeImp extends SimpleNodeImp[DownwardParam, UpwardParam, EdgeParam, UInt] {
  def edge(pd: DownwardParam, pu: UpwardParam, p: Parameters, sourceInfo: SourceInfo) = {
    if (pd.width < pu.width) EdgeParam(pd.width) else EdgeParam(pu.width)
  }
  def bundle(e: EdgeParam) = UInt(e.width.W)
  def render(e: EdgeParam) = RenderedEdge("blue", s"width = ${e.width}")
}

3.4. 节点

3.4.1. 驱动节点

驱动器节点的参数是Seq,因为它输出到加法器和监控器两个节点。

/** node for [[AdderDriver]] (source) */
class AdderDriverNode(widths: Seq[DownwardParam])(implicit valName: ValName)
extends SourceNode(AdderNodeImp)(widths)

3.4.2. 监控器节点

Monitor有3个节点,每个节点只有一个输入参数,但是最终SinkNode模块依然要转为Seq类型。

/** node for [[AdderMonitor]] (sink) */
class AdderMonitorNode(width: UpwardParam)(implicit valName: ValName)
extends SinkNode(AdderNodeImp)(Seq(width))

3.4.3. 加法器节点

如图所示,加法器是电路模块,其节点要求两个函数作为参数,如下:

/** node for [[Adder]] (nexus) */
class AdderNode(dFn: Seq[DownwardParam] => DownwardParam,
                uFn: Seq[UpwardParam] => UpwardParam)(implicit valName: ValName)
extends NexusNode(AdderNodeImp)(dFn, uFn)

3.5. 加法器电路

加法器模块,其内部实例化加法器节点,并通过模式匹配的方法来检查加法器的参数是否符合要求。这里只是检测,并不是协商。然后通过LazyModuleImp来实现具体的电路描述。node的in和out都是Seq,且Seq中存放的都是元组,元组的第1个元素即真实的端口信号。

node.out.head._1,因为out只有一个输出端口,所以取head即可。

node.in.unzip._1.reduce(_ + _),复用reduce来累加所有解包的输入端口。

参数desiredName指定生成的模块名。

/** adder DUT (nexus) */
class Adder(implicit p: Parameters) extends LazyModule {
  val node = new AdderNode (
    { case dps: Seq[DownwardParam] =>
      require(dps.forall(dp => dp.width == dps.head.width), "inward, downward adder widths must be equivalent")
      dps.head
    },
    { case ups: Seq[UpwardParam] =>
      require(ups.forall(up => up.width == ups.head.width), "outward, upward adder widths must be equivalent")
      ups.head
    }
  )
  lazy val module = new LazyModuleImp(this) {
    require(node.in.size >= 2)
    node.out.head._1 := node.in.unzip._1.reduce(_ + _)
  }

  override lazy val desiredName = "Adder"
}

3.6. 驱动器

驱动器中有一个节点,节点有numOutputs个输出,输出参数线宽为width。驱动器和加法器的电路逻辑在LazyModuleImp中实现。

require,检测当前所有输出线宽是不是一样。

生成随机数赋值给输出信号的被加数信号。

/** driver (source)
  * drives one random number on multiple outputs */
class AdderDriver(width: Int, numOutputs: Int)(implicit p: Parameters) extends LazyModule {
  val node = new AdderDriverNode(Seq.fill(numOutputs)(DownwardParam(width)))

  lazy val module = new LazyModuleImp(this) {
    // check that node parameters converge after negotiation
    val negotiatedWidths = node.edges.out.map(_.width)
    require(negotiatedWidths.forall(_ == negotiatedWidths.head), "outputs must all have agreed on same width")
    val finalWidth = negotiatedWidths.head

    // generate random addend (notice the use of the negotiated width)
    val randomAddend = FibonacciLFSR.maxPeriod(finalWidth)

    // drive signals
    node.out.foreach { case (addend, _) => addend := randomAddend }
  }

  override lazy val desiredName = "AdderDriver"
}

3.7. 监控器

监控器模块,主要用来计算驱动器的输入,然后与加法器模块传过来的输出作比较。监控器3个节点,2个来自驱动器,1个来自加法器。具体的电路连接在LazyModuleImp进行。监控器模块添加一个输出信号,用于测试时观察校验结果。

/** monitor (sink) */
class AdderMonitor(width: Int, numOperands: Int)(implicit p: Parameters) extends LazyModule {
  val nodeSeq = Seq.fill(numOperands) { new AdderMonitorNode(UpwardParam(width)) }
  val nodeSum = new AdderMonitorNode(UpwardParam(width))

  lazy val module = new LazyModuleImp(this) {
    val io = IO(new Bundle {
      val error = Output(Bool())
    })

    // print operation
    printf(nodeSeq.map(node => p"${node.in.head._1}").reduce(_ + p" + " + _) + p" = ${nodeSum.in.head._1}")

    // basic correctness checking
    io.error := nodeSum.in.head._1 =/= nodeSeq.map(_.in.head._1).reduce(_ + _)
  }

  override lazy val desiredName = "AdderMonitor"
}

3.8. 顶层模块

顶层模块,将驱动器、加法器和监控器合并起来,组成一个完整的示例。所有参与参数协商的模块,都使用LazyModule。构建2个驱动器、加法器、监控器。

11行的代码将所有驱动节点全部连接到加法器节点。

13行的代码将所有驱动节点同时全部连接到监控器节点,两者个数一样。

14行的代码将加法器节点连接到监控器的nodeSum节点。

实际监控Monitor的结果在LazyModuleImp中实现。

/** top-level connector */
class AdderTestHarness()(implicit p: Parameters) extends LazyModule {
  val numOperands = 2
  val adder = LazyModule(new Adder)
  // 8 will be the downward-traveling widths from our drivers
  val drivers = Seq.fill(numOperands) { LazyModule(new AdderDriver(width = 8, numOutputs = 2)) }
  // 4 will be the upward-traveling width from our monitor
  val monitor = LazyModule(new AdderMonitor(width = 4, numOperands = numOperands))

  // create edges via binding operators between nodes in order to define a complete graph
  drivers.foreach{ driver => adder.node := driver.node }

  drivers.zip(monitor.nodeSeq).foreach { case (driver, monitorNode) => monitorNode := driver.node }
  monitor.nodeSum := adder.node

  lazy val module = new LazyModuleImp(this) {
    when(monitor.module.io.error) {
      printf("something went wrong")
    }
  }

  override lazy val desiredName = "AdderTestHarness"
}

4. 生成Verilog

AdderTestHarness只是LazyModule,真实的Module是其成员module.

object Main extends App {
  emitVerilog(
    LazyModule(new AdderTestHarness()(Parameters.empty)).module,
    Array(
      "--emission-options=disableMemRandomization,disableRegisterRandomization",
      "--info-mode=use",
      "--target-dir=hdl",
      "--full-stacktrace"
    )
  )
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值