天天看點

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

           

繼續閱讀