您的位置:首页 > 财经 > 金融 > Chisel开发Diplomacy框架

Chisel开发Diplomacy框架

2024/11/15 21:03:55 来源:https://blog.csdn.net/feihe027/article/details/142212505  浏览:    关键词: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 negotiationval 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 signalsnode.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 operationprintf(nodeSeq.map(node => p"${node.in.head._1}").reduce(_ + p" + " + _) + p" = ${nodeSum.in.head._1}")// basic correctness checkingio.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 = 2val adder = LazyModule(new Adder)// 8 will be the downward-traveling widths from our driversval drivers = Seq.fill(numOperands) { LazyModule(new AdderDriver(width = 8, numOutputs = 2)) }// 4 will be the upward-traveling width from our monitorval monitor = LazyModule(new AdderMonitor(width = 4, numOperands = numOperands))// create edges via binding operators between nodes in order to define a complete graphdrivers.foreach{ driver => adder.node := driver.node }drivers.zip(monitor.nodeSeq).foreach { case (driver, monitorNode) => monitorNode := driver.node }monitor.nodeSum := adder.nodelazy 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"))
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com