|
一、Diplomacy概述
diplomacy是一个chisel开发团队开发的chisel库,主要实现两个功能:
1)实现模块之间的参数协商。参数在模块之间传递时可以根据需求协商与检查,更加且不容易出错。
2)快速实现设计拓扑的参数化。因为verilog需要大量的define,很容易出错且写起来困难。
那diplomacy是如何做的呢?它是将模块之间的组织关系抽象成一张有向无环图。模块具有结点,相互之间的连接关系是边。
将模块A与模块B的bundle之间的连接抽象成Node之间的连接,输入输出端口统一用Node代替,Node
就是模块的Port。采用bindoperation操作,可以在两个Node之间创建一条边,用于参数协商。两这个
Bind operation本业就是可以在参数化的,有没有这条边都是可以配置的。
二、加法器例子
为了展示diplomacy的上述两个特性,在此实现一个有意思的加法器,加法器的输入端口个数被参数化,同时实现参数的简单的协商,让输入输出
结点都同位宽小的作为实际使用参数。
为了展示拓扑参数化,实现思路如上图。将加法器的输入抽象成driver模块。实现操作的是adder模块。然后来一个checker模块,看看计算结果是不是正确。
driver产生随机数,adder累加,checker看看加的是不是对,输出判断结果。
其中driver的数目是可参数化的,结点之间传递的参数要经过协商,取小的值。
1)配置环境
由于chisel3把这些参数化的库都独立出去了,所以我们要引用rocketchip的库。根据https://blog.csdn.net/qq_39507748/article/details/120587159
这个网页所说,就是需要import freechips.rocketchip.diplomacy等,那么需要在build.sbt中导入相应的库,
在https://zhuanlan.zhihu.com/p/366476459中有提到libraryDependencies += "edu.berkeley.cs" %% "rocketchip" % "1.2.0"
(补充说明:在build.sbt中%%自动给库包的包名结尾加上Scala的版本号,而%用于分割groupid与artifactid)
2)定义传递的参数以及结点实现
case class UpwardParam(width:Int)
case class DownwardParam(width:Int)
case class EdgeParam(width:Int)
因为本例子很简单,我们定义结点向上传递的参数与向下传递的参数均只包含一个int成员变量,实际上这个参数可以非常复杂。
EdgeParam写上的意义在于UpwardParma和DownwardParam商量一下,得出一个最终用的EdgeParam。
由于我们用到的所有结点协商规律是一样的,所以我们只需要定义结点的实现。
object AdderNodeImp extends SimpleNodeImp[DownwardParam,UpwardParam,EdgeParam,UInt] {
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}")
}
通过虚函数edge,bundle的实现来参数协商。其中render是用来画图的一个函数。
如上图,最终给EdgeParam赋值的是位宽小的值,然后用这个EdgeParam赋给bundle,用作真正的连接。
3)定义三种结点
根据上图可知,实际上我们完成这个设计用到了三种结点
a)SourceNode 类型的结点,只有输出没有输入
b)SinkNode 类型的结点,只有输入没有输出
c)NexusNode 类型的结点,输入输出都有若干
不同的结点全定义在Nodes.scala这个文件中,还有些其它的结点,但常用的是这三种。我们定义这三个类:
class AdderDriverNode(width:Seq[DownwardParam])(implicit valName:ValName) extends SourceNode(AdderNodeImp)(widths)
//为什么需要有隐式参数implicit valName:ValName)????????
//我查看SourceNode源代码发现这个类:
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) {
protected[diplomacy] def mapParamD(n:Int,p:Seq[D]) = po
protected[diplomacy] def mapParamU(n:Int,p:Seq[U])=Seq()
}
是这样定义的,而AdderNodeImp是object AdderNodeImp extends SimpleNodeImp{override def edge... override def bundle...}
难道可以将一个object传给SourceNode吗???语法好象与我的理解不一样啊:实验发现代码中这样传object是合法的。
class AdderMonitorNode(width:UpwardParam)(implicit valName:ValName) extends SinkNode(AdderNodeImp)(Seq(width))
//查看SinkNode源码,觉得应该改为:
class AdderMonitorNode(widths:Seq[UpwardParam])(implicit valName:ValName) extends SinkNode(AdderNodeImp)(widths)
class AdderNode(dFn:Seq[DownwardParam]=>DownwardParam,uFn:Seq[UpwardParam]=>UpwardParam)(implicit valName:ValName) extends NexusNode(AdderNodeImp)(dFn,uFn)
4)定义Adder模块
class Adder(implicit p: Parameters) extends LazyModule {
//这个Adder模块中有一个继承自NexusNode(具有若干输入和输出的节点)的AdderNode
//在AdderNode中对这些输入和输出节点进行过滤处理
val node = new AdderNode(//AdderNode是有若干输入和输出的NexusNode节点
{
dFn:Seq[DownwardParam]=>require(dFn.forall(p=>p.width == dFn.head.width),"inward,downward widths not equivalent")
dFn.head
},
{
uFn:Seq[UpwardParam]=>require(uFn.forall(p=>p.width == uFn.head.widthd),"outward,upward widths not equivalent")
uFn.head
}
)
//既然LazyModuleImp其实就是MutiIOModule,那就说明val module就代表着一个多端口的模块
lazy val module: LazyModuleImp = new LazyModuleImp(this) { //LazyModuleImp继承自MutiIOModule with LazyModuleImpLike,
//而trait LazyModuleImpLike又继承自RawModule,并且MutiIOModule也是继承自RawModule
//也就是说有class R,class M extends R,trait L extend R,class LMI extends M with L
required(node.in.size >= 2)
node.out.head._1
}
override lazy val desiredName = "Adder" //模块名称
}
5)定义driver模块
class AdderDriver(width:Int,numoutputs:Int)(implicit p: Parameters) extends LazyModule {
//因为AdderDriverNode继承自SourceNode表示只有输出没有输入的节点,所以在此创建这个节点实例,主构造参数是Seq[DownwardParam]
//numoutputs表示有多少个DownwardParam,每个DownwardParam的位宽是width,width是要参与协商的位宽参数
val node = new AdderDriverNode(Array.fill(numoutputs)(DownwardParam(width)))
lazy val module = new LazyModuleImp(wrapper = this) {
/*
AdderDriverNode 只有一个输出边edge,也即node.edges.out(其实是因为AdderDriverNode<--SourceNode<--MixedNode,在Nodes.scala中
case class Edges[EI,EO](in:EI,out:EO),而sealed abstract class MixedNode[DI, UI, EI, BI <: Data, DO, UO, EO, BO <: Data]
中有lazy val edges:Edges(edgesIn,edgesOut), protected[Diplomacy] lazy val edgesIn: Seq[EI]=(iPorts zip uiParams).map{..},
lazy val edgesOut:Seq[EO]=(oPorts zip doParams).map{..})
node.edges.out类型是case class Edges[EI,EO)(in:EI,out:EO)中的out,实际传入的是Seq[EO],是一个集合类型,但实际元素类型是EO,
代表协商后的参数(具体是DownwardParam还是width???),而不是数据,数据是BO。这是因为
AdderDriver需要使用协商后的位宽参数,所以这里才访问了node.edges.out,并不是只有这里才有???。这里的out包含两个EO,因为有两个
AdderDriverNode,各自有一个边edge,所以协商参数有两个。记住协商时是以边edge为单位,也即每个边edge都会参与协商,所以才会有两个协商参数,
取head即可
*/
/*
protected[deplomacy] lazy val edgesOut = (oPorts zip doParams).map{case((i,n,p,s),o) => outer.edgeO(o,n,uiParams(i),p,s)}
其中oPorts的类型是List[Int,InWardNode[DO,UO,BO],Parameters,SourceInfo],doParams 的是Seq[DO],在lazy val doParams:Seq[DO]={。。}
的语句块中有val o = mapParamsD(oPorts.size,diParmas),在MixdNode中def mapParmasD(n:Int,p:Seq[DI]):Seq[DO]以及def mapParamsU(
n:Int,p:Seq[UO]):Seq[UI]都是抽象方法,在SourceNode类中(只有输出没有输入端口)mapParamsU是Seq(),而mapParamsD值=po,而po是SourceNode
的主构造器中的参数po:Seq[D],也就是本例中的Array.fill(numoutputs)(DownwardParam(width))的值即Seq[DownwardParam],
经过mapParamsD处理之后,再o.map(outer.mixO(_,node=this)处理,再看outer:
outer 是sealed abstract class MixedNode的主构造参数val outer:OutwardNodeImp[DO,UI,EI,BI]
这个 outer 值就是我们的class AdderDriverNode 传入的第二个参数: object AdderNodeImp extends SimpleNodeImp[DownardParam,UpwardParam,EdgeParam,UInt]
关于OuterwardNodeImp和SimpleNodeImp的关系:
trait OutwardNodeImp[Do,UO,EO,BO <: Data] {
//DO是指Downwards Parameters,当然是从节点的输出口
//UO是指Upwards Parameters,当然也是节点的输出口
//EO是指Edge Parameters,输出端口相连的边
//BO是节点的输出边相连的Bundle
def edgeO(pd: DO,pu:UO,p: Parameters,sourceInfo:SourceInfo):EO
def bundle(eo:EO):BO
//还有二个可选的方法def mixO,def getI
def mixO(pd: DO,node:OutwardNode[DO,UO,BO]): DO = pd
}
//既然OutwardNodeImp是一个trait,名称其实不应该带有Imp,估计是为了配合NodeImp才带了一个Imp
abstract class NodeImp[D,U,EO,EI,B< ata] extends Object with InwardNodeImp[D,U,EI,B] with OutwardNodeImp[D,U,EO,B]
abstract class SimpleNodeImp[D,U,E,B] extends NodeImp[D,U,E,E,B]
可以总结出来: SimpleNodeImp[D,U,E,B] <- NodeImp[D,U,EI,EO,B] <- OutwardNodeImp[D,U,EO,B]
也就是说outer.mixO(..)其实是我们的AdderNodeImp在进行mixO处理,在trait OutwardNodeImp 中的def mixO返回的是其第一个参数pd: DO
所以最后o.map返回的Seq,其中的元素类型还是我们的 AdderNodeImp规定的case class DownwardParam,也就是说doParams的类型也是Seq[DownardParam]
(oPorts zip doParams)的结果就是一个List,但List中元素数量以oPorts和doParams较短者为最终数量,每个元素类型是元组,比如(oPorts(0),doParams(0))
doParams(0)就是我们传入的DownwardParam
再来看oPorts(0),源码中:lazy val oPorts = oBindings.flatMap{...}
而oBindings的类型是:List[(Int,InwardNode[DO,UO,BO],NodeBinding,Parameters,SourceInfo)]
oBinding.flatMap{case (i表示Int,n表示InwardNode[DO,UO,BO],_,p表示Parameters,s表示SourceInfo) =>
val (start,end) = n.iPortMapping(i)
(start until end) map { j => (j,n,p,s)}
}
val (start,end) = n.iPortMapping(i)其实是 trait InwardNode[DO,UO,BO] extends BaseNode {
private val accPI = ListBuffer[(Int,OutwardNode[DI,UI,BI],NodeBinding,Parameters,SourceInfo)]() //在def oPush中添加元素,并且是在子类MixedNode的def bind添加
protected[diplomacy] val iPortMapping:Seq[(Int,Int)]
protected[diplomacy] val iStar:Int
protected[diplomacy] val diParams:Seq[DI] //从连接的节点中来的 DI: Downward Parameters from inward ports
protected[diplomacy] val uiParams:Seq[UI] //从本节点中来的 UI:Upward Parameters from inward ports
}的iPortMapping(i)
InwardNode 中的抽象变量都在子类MixedNode中用protected[diplomacy] lazy val (oPortMapping,iPortMapping,oStar,iStar)=...实例化
再来分析protected[deplomacy] lazy val degesOut = (oPorts zip doParams).map语句块中的case((i,n,p,s),o)=>outer.edgeO(o,n.uiParams(i),p,s):
outer 是我们传进来的object AdderNodeImp extends SimpleNodeImp[DownwardParam,UpwardParam,EdgeParam,UInt]
在SimpleNodeImp类中 def edge0(pd: D(其实是DownwardParam),pu:U(其实是UpwardParam),p: Parameters,sourceInfo:SourceInfo):E(其实是EdgeParam)
= edge(pd,pu,p,sourceInfo)
在SimpleNodeImp类中抽象方法def edge(pd (其实是DownwardParam),pu:U(其实是UpwardParam),p arameters,sourceInfo:SourceInfo):E(其实是EdgeParam)
当然SimpleNodeImp类中还有一个抽象方法def bundle(e:E(其实是EdgeParam)):B(其实就是我们的UInt)
所以outer.edge0方法就是调用我们的AdderNodeImp这个SimpleNodeImp实现类中的def edge方法,而我们的edge方法根据位宽选较小位宽的返回
*/
//node.edges.out其实就是MixedNode中的val edges = Edges(edgesIn,edgesOut)的 edgesOut,根据上面分析可知它是调用了我们的
//节点协商实现类(object AdderNodeImp ) 中的def edge,已经获得了协商结果即EdgeParam类型值,当然是List[EdgeParam]
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是AdderDriverNode extends SourceNode,它的out是在MixedNode中定义的def out:Seq[(BO,EO)] 这个out方法用于在LazyModuleImp中访问协商的结果
//def out:Seq[(BO,EO)] = { bundleOut zip edgesOut} //bundleOut调用我们的节点协商实现类(object AdderDriverNode)中的def bundle根据协商结果最终获得对应的数据类型值即UInt(width)
//当然,bundleOut返回的也是Seq[BO]即Seq[UInt]
node.out.foreach { case (addend,_) => addend := randomAddend } //获得了addend:BO,即UInt类型,然后再连线到某个随机值
}
override lazy val desiredName = "AdderDriver"
}
//6)定义Monitor模块
class AdderMonitor(width:Int,numOperands:Int)(implicit p: Parameters) extends lazyModule {
//AdderMonitorNode extends SinkNode(AdderNodeImp) 只有输入没有输出的节点
//每个lazyModule模块中,需要一个继承自SourceNode或SinkNode或NexusNode的节点类,除了这个类需要的参数Seq[DownwardParam]或Seq[UpwardParam]
//或Seq[DownwardParam]=>DownwardParam,Seq[UpwardParam]=>UpwardParam外,所有这些节点类还需要将一个节点参数协商类即NodeImp的实现类传给
//对应的父类:SourceNode或SinkNode或NexusNode,一般来说这个参数协商类要用object(单一性)来定义,就象本例中object AdderNodeImp,以示协商的标准一致
//rocket chip库中已经定义了一些这样的参数协商object,比如:SimpleNodeImp,查源码发现还有一个object TLImp(用于TileLink总线,位于tilelink\Nodes.scala)
//object TLImp extends NodeImp[TLClientPortParaeters,TLManagerPortParameters,TLEdgeOut,TLEdgeIn,TLBundle] {
// def edgeO(pd:TLClientPortParameters,pu:TLManagerPortParameters,p: Parameters,sourceInfo:SourceInfo)..
// def edgeI(pd:TLClientPortParameters,pu:TLManagerPortParameters,p: PARAMETERS,SOURCEInfo:SourceInfo)...
// def bundleO(eo:TLEdgeOut)...
// def bundleI(ei:TLEdgeIn)...
// def render(ei:TLEdgeIn)...
// override def monitor(bundle:TLBundle,edge:TLEdgeIn)...
// override def mixO..
// override def mixI...
//}
/*
class SinkNode[D,U,EO,EI,B<: Data](imp:NodeImp[D,U,EO,EI,B])(pi:Seq[U])(implicit valName:ValName) extends MexedNode(imp,imp) {
protected[diplomacy] def mapParamsD(n:Int,p:Seq[D]):Seq[D] = Seq()
protected[diplomacy] def mapParamsU(n:Int,p:Seq[U]):Seq[U] = pi
}
*/
//两个节点,二个连driver,一个连adder
val nodeSeq = Array.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())
})
/*
sealed abstract class MixedNode[DI,UI,EI,BI< ata,DO,UO,EO,BO< ata](
val inner:InwardNodeImp[DI,UI,EI,BI],
val outer:OutwardNodeImp[DO,UO,EO,BO])(implicit valName:ValName) extends BaseNode with NodeHandle with InwardNode with OutwardNode
{
def in:Seq[(BI,EI)] = { bundleIn zip edgesIn}
//e:EI,EI其实就是我们的EdgeParam,inner其实就是我们的继承自 SimpleNodeImp 协商实现类 AdderNodeImp
//所以inner.bundleI(e)最终会调用我们的协商实现类 AdderNodeImp 中的def bundle(e:EdgeParam):UInt
lazy val bundleIn:Seq[BI] = edgesIn.map(e=>Wire(inner.bundleI(e)))
//n:OutwardNode[DI,UI,BI],o:Int,i:UI ,其中,DI是DownwardParam,UI是UpwardParam,BI是UInt
lazy val edgesIn = (iPorts zip uiParams).map{case((o,n,p,s),i)=>inner.edgeI(n,doParams(o),i,p,s)}
//trait InwardNode[DI,UI,BI< ata] extends BaseNode {
// private val accPI = ListBuffer[(Int,OutwardNode[DI,UI,BI],NodeBinding,Parameters,SourceInfo)]() //实际上DI,UI,BI分别是我们定义的Down/UpwardParam和UInt
// lazy val iBindings = {iRealized = true;accPI.result()}
// def iPushed = accPI.size
// def iPush(index:Int,node:OutwardNode[DI,UI,BI],binding:NodeBinding)(implicit p: Parameters,sourceInfo:SourceInfo){ accPI += ((index,node,binding,p,sourceInfo))
// val iStar:Int
// val iPortMapping:Seq[(Int,Int)]
// val diParams:Seq[DI] //from connected nodes
// val uiParams:Seq[UI] //from this node
// def bind(h: outwardNode[DI,UI,BI],binding:NodeBinding)(implicit p: Parameters,sourceInfo:SourceInfo):Unit
//}
//再来看 OutwardNode 的代码:
//trait OutwardNode[DO,UO,BO< ata] extends BaseNode {
// private val accPO = ListBuffer[(Int,InwardNode[DO,UO,BO],NodeBinding,parameters,SourceInfo)]()
// def oPushed:Int = accPO.size
// def oPush(index:Int,node:InwardNode[DO,UO,BO],binding:NodeBinding)(implicit p arameters,sourceInfo:SourceInfo) {
// accPO += ((index,node,binding,p,sourceInfo))
// }
// lazy val oBinding = {..;accPO.result()}
// val oStar:Int
// val oPortMapping:Seq[(Int,Int)]
// val uoParams:Seq[UO] //from connected nodes
// val doParams:Seq[DO] //from thsi node
//}
//OutwardNode中的oPortMapping:Seq[(Int,Int)]是在MixedNode中通过lazy val (oPortMapping, iPortMapping, oStar, iStar)实例化的
lazy val iPorts = iBindings.flatMap{case(i,n_,p,s)=>
val (start,end) = n.oPortMapping(i) //n是 outwardNode[DI,UI,BI]
(start until end) map {j => (j,n,p,s)}
}
lazy val uiParams:Seq[UI] = { //通过下面的分析可知 uiParams 就是我们传入的Seq[UpwardParam]参数值即widths:Seq[UpwardParam]
try {
...
//mapParamsU 在MixedNode中是一个抽象方法,本AdderMonitor中用的是AdderMonitorNode extends SinkNode,SinkNode extends MixedNode
//在SinkNode中有实现:
//class SinkNode[D,U,EO,EI,B< ata](imp:NodeImp[D,U,EO,EI,B])(pi:Seq[U])(implicit valName:ValName) extends MixedNode(imp,imp) {
// protected[diplomacy] def mapParamsD(n:Int,p:Seq[D]):Seq[D]=Seq()
// protected[diplomacy] def mapParamsU(n:Int,p:Seq[U]):Seq[U] = pi
//}
val i = mapParamsU(iPorts.size,uoParams) //所以i值还是我们传入的Seq[UpwardParam]
i.map(inner.mixI(_,node=this)) //inner是我们的 AdderNodeImp 协商实现类 <-SimpleNodeImp <-NodeImp <- InwardNodeImp以及InwardNode
//在InwardNodeImp中有def mixI(pu:UI,node:InwardNode[DI,UI,BI]):UI=pu // insert node into parameters
//所以i.map返回的集合就是Seq[UpwardParam]即Seq[UI]
}catch {
case c:.....
}
}
}
*/
//nodeSum是只有输入没有输出的SinkNode,in是MixedNode中的def in:Seq[(BI,EI)] = {bundleIn zip edgesIn} ,BI和EI本模块中是UInt和EdgeParam
//nodeSum.in.head._1获得的是BI即UInt数据
//二个节点连到Adder,nodeSeq.map(_.in.head._1),将两个节点
io.error := nodeSum.in.head._1 =/= nodeSeq.map(_.in.head._1).reduce(_ + _)
}
override lazy val desiredName = "AdderMonitor"
}
|
|