一、Dipomacy 概论
diplomacy是一个chisel开发团队开发的chisel库,主要实现两个功能:
1)实现模块之间的参数协商。参数在模块之间传递时可以根据需求协商与检查,更加灵活且不容易出错。
2)快速实现设计拓扑的参数化。使用verilog实现设计拓扑的参数化是非常困难的一件事,往往包含着大量的define,容易出错,且写起来困难。
dipomacy是怎么做的呢?它是将模块之间的组织关系抽象成一张有向无环图。模块具有结点,相互之间的连接关系是边。如下图所示:
将模块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 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))))
}