一、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 < 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))))
}