天天看点

chisel相比verilog优势之二:高级参数化---diplomacy机制

一、Dipomacy 概论

diplomacy是一个chisel开发团队开发的chisel库,主要实现两个功能:

1)实现模块之间的参数协商。参数在模块之间传递时可以根据需求协商与检查,更加灵活且不容易出错。

2)快速实现设计拓扑的参数化。使用verilog实现设计拓扑的参数化是非常困难的一件事,往往包含着大量的define,容易出错,且写起来困难。

dipomacy是怎么做的呢?它是将模块之间的组织关系抽象成一张有向无环图。模块具有结点,相互之间的连接关系是边。如下图所示:

chisel相比verilog优势之二:高级参数化---diplomacy机制

将模块A与模块B的

bundle

之间的连接抽象成

Node

之间的连接,输入输出端口统一用Node代替,Node就是模块的Port。采用

bind operation

操作符,可以在两个Node之间创建一条边,用于参数协商。而这个

Bind Operation

本来就是可以参数化的,有没有这条边都是可以配置的。

更加详细的介绍可以参考一位知乎大佬写的文章,里面还有具体的加法器的例子:

  • Chisel初体验—高级参数化PART2: Dipomacy机制(六)

二、代码注释

下面贴出加法器的代码,并且加上了自己的理解注释:

package wzx
/*
*   step 1 导入所需的包
* */
import chisel3._
import chisel3.stage._
import freechips.rocketchip.config.{Config,Parameters}
import chisel3.internal.sourceinfo.SourceInfo
import chisel3.util.random.FibonacciLFSR
import freechips.rocketchip.diplomacy.{SimpleNodeImp,RenderedEdge,NexusNode,SinkNode,SourceNode,
                                       LazyModule,LazyModuleImp,ValName}
/*
*   step 2 定义传递的参数以及结点实现
* */
case class UpwardParam(width: Int)
case class DownwardParam(width: Int)
case class EdgeParam(width: Int)

/***********************************************************************************************
      这里是继承了SimpleNodeImp而不是NodeImp,这是因为AdderNode的两个edge协商规则一样
      只需要传入一个EdgeParam即可。SimpleNodeImp继承自NodeImp,会给NodeImp的EI和EO传入同一个EdgeParam。

      可以理解为,那些有两个edge的node(左边的叫inner side,右边叫outer side),
      AdderNodeImp要包含两部分:InwardNodeImp、OutwardNodeImp
      如果两个edge的协商规则不一样,AdderNodeImp就继承自NodeImp,然后分别实现edgeO和edgeI的协商逻辑。

      有一点需要注意的是,DownwardParam和UpwardParam是不分I和O的,因为这俩本身就带有方向,
      前者是由某个node(Driver)的outer side产生,然后由与其相连的node(Adder)的inner side接收的参数;
      后者是由某个node(Monitor)的inner side产生,然后由与其相连的node(Adder)的outer side接收的参数;
      并且这两个参数都可以有多个。
 ***********************************************************************************************/

object AdderNodeImp extends SimpleNodeImp[DownwardParam,UpwardParam,EdgeParam,UInt]{
  //最终给EdgeParam赋值的是位宽小的值,然后用这个EdgeParam赋给bundle,用作真正的连接。
  def edge(pd: DownwardParam,pu: UpwardParam,p: Parameters, sourceInfo: SourceInfo): EdgeParam = {
      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}")
}
/*
*   step 3 定义三种结点
*   1、AdderDriverNode传入一个Seq[DownwardParam],也即多个DownwardParam,
*   是因为和需要使用协商结果的AdderNode相连的有两个AdderDriverNode,它们各自提供一个DownwardParam,
*   记住DownwardParam是以node为单位的。
*
*   2、AdderMonitorNode只有一个UpwardParam,是因为只有一个AdderMonitorNode
*   和需要使用协商结果的AdderNode相连,所以只需要一个UpwardParam,但参数也按照Seq(width)传给了超类。
* */

class AdderDriverNode(widths: Seq[DownwardParam])(implicit valName: ValName)
      extends SourceNode(AdderNodeImp)(widths)

class AdderMonitorNode(width: UpwardParam)(implicit valName: ValName)
      extends SinkNode(AdderNodeImp)(Seq(width))

class AdderNode(dFn: Seq[DownwardParam] => DownwardParam,
                uFn: Seq[UpwardParam] => UpwardParam)(implicit valName: ValName)
      extends NexusNode(AdderNodeImp)(dFn,uFn)
/*
*   step 4 定义Adder模块
* */
class Adder(implicit p: Parameters) extends LazyModule {
  val node = new AdderNode(

    //入参dps和ups是返回DownwardParam和UpwardParam的偏函数
    //下面的require函数是保证有多个Param时,同一方向的Param的值要保持一致
    //比如在这个例子中就是指两个AdderDriverNode不能给AdderNode不一样的位宽参数DownwardParam。

    //最后将head也就是第一个Param返回即可,因为值都是一样的,返回第一个就行了
    { dps: Seq[DownwardParam] =>
      require(dps.forall(dp => dp.width == dps.head.width), "inward,downward widths not equivalent")
      dps.head
    },
    { ups: Seq[UpwardParam] =>
      require(ups.forall(up => up.width == ups.head.width), "outward,upward widths not equivalent")
      ups.head
    }
  )
  //注意这里的in和out只包含B和E,没有D、U,也就是DownwardParam和UpwardParam
  //其实in和out包括传递的输入输出数据和协商后的参数,B代表数据,E代表协商后的参数。
  //一个B其实就可以认为是一个端口,注意这里指的不是一组端口而是一个端口。
  lazy val module: LazyModuleImp = new LazyModuleImp(wrapper = this) {
    require(node.in.size >= 2)
    //这里是因为只有一个输出,由于node.out是Seq[(BO, EO)],所以直接.head._1把输出BO取出来赋值
    //这里因为有多个输入,由于node.in是Seq[(BI, EI)...],所以直接.unzip._1把输入BI的列表取出来计算
    node.out.head._1 := node.in.unzip._1.reduce(_ + _)
  }

  override lazy val desiredName = "Adder"
}
/*
*   step 5 定义Driver模块
* */
class AdderDriver(width: Int,numOutputs: Int)(implicit p: Parameters) extends LazyModule {
  //创建AdderDriverNode,width是要参与协商的位宽参数,numOutputs代表有几个AdderDriverNode
  val node = new AdderDriverNode(Seq.fill(numOutputs)(DownwardParam(width)))

  lazy val module = new LazyModuleImp(wrapper = this) {
    // check that node parameters converge after negotiation

    /****************************************************************************************
        AdderDriverNode只有一个输出边edge,也即node.edges.out
        node.edges.out类型是EO,代表的是协商后的参数;而不是数据,数据是B。
        这里是因为AdderDriver需要使用协商后的位宽参数,所以这里才访问了node.edges.out,并不是只有这里才有。
        这里的out包含两个EO,因为有两个AdderDriverNode,各自有一个输出边edge,所以协商参数有两个。
        记住协商时是以边edge为单位的,也即每个边edge都会参与协商,所以才会有两个协商参数,取head即可。
     ****************************************************************************************/

    val negotiatedWidths = node.edges.out.map(_.width)
    val finalWidth = negotiatedWidths.head
    // generate random addend (notice the use of the negotiated width)
    val randomAddend = FibonacciLFSR.maxPeriod(finalWidth)
    // drive signals 每次赋值都会产生不一样的值。
    // node.out是Seq[(BO, EO)],这里其实就是只对BO进行赋值,本例中有两个BO
    node.out.foreach { case (addend,_) => addend := randomAddend }
  }

  override lazy val desiredName = "AdderDriver"
}
/*
*   step 6 定义Monitor模块
* */
class AdderMonitor(width: Int, numOperands: Int)(implicit p: Parameters) extends LazyModule {
  //创建node,两个连接driver的node,一个连接adder的node。
  val nodeSeq = Seq.fill(numOperands){ new AdderMonitorNode(UpwardParam(width))}
  val nodeSum = new AdderMonitorNode(UpwardParam(width))

  lazy val module = new LazyModuleImp( wrapper = this ){
      val io = IO(new Bundle {
        val error = Output(Bool())
      })
      //node如果只有单输入,单输出,那么使用的就是.in(out).head._1,得到B
      //node如果有多输入输出,那么使用的就是.in(out).unzip._1,得到B的列表
      io.error := nodeSum.in.head._1 =/= nodeSeq.map(_.in.head._1).reduce(_ + _)
  }

  override lazy val desiredName = "AdderMonitor"
}
/*
*   step 7 连接三个模块
* */
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

  /*******************************************************************************
      连接node时就直接连接即可,不用考虑比如该例中的每个driver.node有两根线,用不用区分的问题;
      也不用考虑连接的时候是连的该node的输入端还是输出端,都直接用:= 连接上即可。
  ********************************************************************************/
  // 连接adder和driver之间的node
  drivers.foreach{ driver => adder.node := driver.node }
  // 连接driver和monitor之间的node
  drivers.zip(monitor.nodeSeq).foreach { case (driver,monitorNode) => monitorNode := driver.node }
  // 连接monitor和adder之间的node
  monitor.nodeSum := adder.node

  lazy val module = new LazyModuleImp(wrapper = this){
    val io = IO(new Bundle{
      val finished = Output(Bool())
    })
    when(monitor.module.io.error) {
      printf("something went wrong")
    }
    io.finished := monitor.module.io.error
  }

  override lazy val desiredName = "AdderTestHarness"
}
/*
*   step 8 生成结果
* */
class AdderDiplomacy()(implicit p:Parameters) extends Module {
  val io = IO(new Bundle {
    val success = Output(Bool())
  })
  val ldut = LazyModule(new AdderTestHarness())
  val dut = Module(ldut.module)
  io.success := dut.io.finished
}
// Generate the Verilog code
object AdderDiplomacy extends App {
  println("Generating the AdderDiplomacy hardware")
  (new ChiselStage).execute(Array("--target-dir", "generated/AdderDiplomacy"),
    Seq(ChiselGeneratorAnnotation(() => new AdderDiplomacy()(Parameters.empty))))
}

           

继续阅读