Diplomacy是一个参数协商框架,用于生成参数化的协议实现。在这个例子中,我们演示如何创建一个简单的参数化加法器与对应的测试模块,我们希望创建一个2-to-1的加法器、一个顶层测试模块、两个driver、以及一个monitor,如下所示
我们同时还想要对这些模块的接口进行参数化,当进行参数协商时,我们的协议希望两个driver可以提供相同宽度的数据,然后使用两个宽度中较小的那个来对monitor进行初始化,注意这和Chisel默认的行为相反。
1
2
3
4
5
6
7
import chipsalliance.rocketchip.config.{Config, Parameters}
import chisel3._
import chisel3.internal.sourceinfo.SourceInfo
import chisel3.stage.ChiselStage
import chisel3.util.random.FibonacciLFSR
import freechips.rocketchip.diplomacy.{SimpleNodeImp, RenderedEdge, ValName, SourceNode,
NexusNode, SinkNode, LazyModule, LazyModuleImp}
参数协商和传递
我们希望数据宽度这一参数可以被不同模块共享,任何想要发送或接收参数信息的模块都需要有一个或多个节点。每一对节点之间可能存在一条或多条有向边,通过边进行参数协商,保证连接的节点之间参数一致。我们可以像如下所示来传递参数,注意该图必须是无环的,我们定义往sink的方向是向下的,往source的方向是向上的。
参数
在这个例子中,我们希望所有电路都使用Scala Int作为数据宽度的类型,我们定义如下case class
。
1
2
3
case class UpwardParam(width: Int)
case class DownwardParam(width: Int)
case class EdgeParam(width: Int)
节点实现
在节点实现(即NodeImp
中),我们描述参数如何在我们的图中流动,以及如何在节点之间协商参数。边参数(E)描述了需要在边上传递的数据类型,在这个例子中就是Int;捆绑参数(B)描述了模块之间硬件实现的参数化端口的数据类型,在这个例子中则为UInt。此处edge
函数实际执行了节点之间的参数协商,比较了向上和向下传播的参数,并选择数据宽度较小的那个作为协商结果。
1
2
3
4
5
6
7
8
// PARAMETER TYPES: D U E B
object AdderNodeImp extends SimpleNodeImp[DownwardParam, UpwardParam, EdgeParam, UInt] {
def edge(pd: DownwardParam, pu: UpwardParam, p: Parameters, sourceInfo: SourceInfo) = {
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}")
}
节点
节点可以接收或生成参数,通过Diplomacy参数化的模块必须有一个或多个节点才能接收或发送参数。除了向上和向下两个方向之外,还有向内和向外两个方向属性。对一个节点而言,指向该节点的边是相对于该节点的向内边,反之则为向外边。
我们继续为driver创建节点,即SourceNode
。由于SourceNode
只沿向外边生成向下流动的参数,节点实现和之前一样。对AdderDriverNode
而言,类型为Seq[DownwardParam]
的widths
表示初始化该节点(AdderDriver
)的模块时输出的数据宽度,这里使用Seq
是因为每个节点可能驱动多个输出,在这个例子中,每个节点会连接到加法器和monitor。
1
2
3
/** node for [[AdderDriver]] (source) */
class AdderDriverNode(widths: Seq[DownwardParam])(implicit valName: ValName)
extends SourceNode(AdderNodeImp)(widths)
Monitor节点也一样,但是为SinkNode
,只从向内边接收上游传来的参数,该节点接收的参数是类型为UpwardParam
的width
。
1
2
3
/** node for [[AdderMonitor]] (sink) */
class AdderMonitorNode(width: UpwardParam)(implicit valName: ValName)
extends SinkNode(AdderNodeImp)(Seq(width))
加法器节点接收两个AdderDriverNode
的输入,并把输出传递给monitor,该节点为NexusNode
。dFn
将向内边传来的向下的参数,映射到向外边的向下的参数,uFn
将向外边的向上的参数,映射到向内边的向上的参数。
1
2
3
4
/** node for [[Adder]] (nexus) */
class AdderNode(dFn: Seq[DownwardParam] => DownwardParam,
uFn: Seq[UpwardParam] => UpwardParam)(implicit valName: ValName)
extends NexusNode(AdderNodeImp)(dFn, uFn)
创建LazyModule
Lazy的意思是指将表达式的evaluation推迟到需要的时候。在创建Diplomacy图之后,参数协商是lazy完成的,因此我们想要参数化的硬件也必须延迟生成,因此需要使用LazyModule
。需要注意的是,定义Diplomacy图的组件(在这个例子里为节点)的创建不是lazy的,模块硬件需要写在LazyModuleImp
。
在这个例子中,我们希望driver将相同位宽的数据输入到加法器中,monitor的数据来自加法器的输出以及driver,所有这些数据位宽都应该相同。我们可以通过AdderNode
的require
来限制这些参数,将DownwardParam
向下传递,以及将UpwardParam
向上传递。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** adder DUT (nexus) */
class Adder(implicit p: Parameters) extends LazyModule {
val node = new AdderNode (
{ case dps: Seq[DownwardParam] =>
require(dps.forall(dp => dp.width == dps.head.width), "inward, downward adder widths must be equivalent")
dps.head
},
{ case ups: Seq[UpwardParam] =>
require(ups.forall(up => up.width == ups.head.width), "outward, upward adder widths must be equivalent")
ups.head
}
)
lazy val module = new LazyModuleImp(this) {
require(node.in.size >= 2)
node.out.head._1 := node.in.unzip._1.reduce(_ + _)
}
override lazy val desiredName = "Adder"
}
AdderDriver
随机生成位宽为finalWidth
的数据,并传递到numOutputs
个source。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** driver (source)
* drives one random number on multiple outputs */
class AdderDriver(width: Int, numOutputs: Int)(implicit p: Parameters) extends LazyModule {
val node = new AdderDriverNode(Seq.fill(numOutputs)(DownwardParam(width)))
lazy val module = new LazyModuleImp(this) {
// check that node parameters converge after negotiation
val negotiatedWidths = node.edges.out.map(_.width)
require(negotiatedWidths.forall(_ == negotiatedWidths.head), "outputs must all have agreed on same width")
val finalWidth = negotiatedWidths.head
// generate random addend (notice the use of the negotiated width)
val randomAddend = FibonacciLFSR.maxPeriod(finalWidth)
// drive signals
node.out.foreach { case (addend, _) => addend := randomAddend }
}
override lazy val desiredName = "AdderDriver"
}
AdderMonitor
打印加法器输出并检测错误,有两个AdderMonitorNode
节点从AdderDriver
接收加法的两个输入,以及一个AdderMonitorNode
节点从加法器接收加法的输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** monitor (sink) */
class AdderMonitor(width: Int, numOperands: Int)(implicit p: Parameters) extends LazyModule {
val nodeSeq = Seq.fill(numOperands) { new AdderMonitorNode(UpwardParam(width)) }
val nodeSum = new AdderMonitorNode(UpwardParam(width))
lazy val module = new LazyModuleImp(this) {
val io = IO(new Bundle {
val error = Output(Bool())
})
// print operation
printf(nodeSeq.map(node => p"${node.in.head._1}").reduce(_ + p" + " + _) + p" = ${nodeSum.in.head._1}")
// basic correctness checking
io.error := nodeSum.in.head._1 =/= nodeSeq.map(_.in.head._1).reduce(_ + _)
}
override lazy val desiredName = "AdderMonitor"
}
创建顶层模块
顶层模块负责初始化所有模块,注意这里driver和monitor初始化了不同的width
,但是参数会通过Diplomacy框架进行协商。我们通过:=
来创建Diplomacy图。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** top-level connector */
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
drivers.foreach{ driver => adder.node := driver.node }
drivers.zip(monitor.nodeSeq).foreach { case (driver, monitorNode) => monitorNode := driver.node }
monitor.nodeSum := adder.node
lazy val module = new LazyModuleImp(this) {
when(monitor.module.io.error) {
printf("something went wrong")
}
}
override lazy val desiredName = "AdderTestHarness"
}
生成Verilog
以下是生成电路的Verilog代码。
1
2
3
val verilog = (new ChiselStage).emitVerilog(
LazyModule(new AdderTestHarness()(Parameters.empty)).module
)
也可以用以下代码替代。
1
2
3
4
5
object Elaborate extends App {
(new ChiselStage).execute(args, Seq(chisel3.stage.ChiselGeneratorAnnotation(
() => LazyModule(new AdderTestHarness()(Parameters.empty)).module))
)
}
附录:环境配置
build.sbt
配置如下,需要注意的是不要直接使用dependsOn
来添加对rocket-chip的依赖,而是需要通过git submodule来添加,并手动设置rocket-chip的路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
val chiselVersion = "3.5.4"
scalaVersion := "2.12.16"
lazy val commonSettings = Seq(
scalacOptions ++= Seq(
"-language:reflectiveCalls",
"-deprecation",
"-unchecked",
"-feature",
"-Xsource:2.11"
),
libraryDependencies ++= Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value),
libraryDependencies ++= Seq("org.json4s" %% "json4s-jackson" % "3.6.12"),
libraryDependencies ++= Seq("org.scalatest" %% "scalatest" % "3.2.12" % "test"),
addCompilerPlugin(("org.scalamacros" % "paradise" % "2.1.1").cross(CrossVersion.full))
)
lazy val chiselSettings = Seq(
libraryDependencies ++= Seq(
"edu.berkeley.cs" %% "chisel3" % chiselVersion,
"edu.berkeley.cs" %% "chiseltest" % "0.5.4"
),
addCompilerPlugin(("edu.berkeley.cs" % "chisel3-plugin" % chiselVersion).cross(CrossVersion.full))
)
lazy val `api-config-chipsalliance` = (project in file("rocket-chip/api-config-chipsalliance/build-rules/sbt"))
.settings(commonSettings)
lazy val hardfloat = (project in file("rocket-chip/hardfloat"))
.settings(commonSettings, chiselSettings)
lazy val rocketMacros = (project in file("rocket-chip/macros"))
.settings(commonSettings)
lazy val rocketchip = (Project("rocket-chip", file("rocket-chip/src")))
.settings(commonSettings, chiselSettings)
.settings(
Compile / scalaSource := baseDirectory.value / "main" / "scala",
Compile / resourceDirectory := baseDirectory.value / "main" / "resources"
)
.dependsOn(`api-config-chipsalliance`)
.dependsOn(hardfloat)
.dependsOn(rocketMacros)
lazy val example = (project in file("."))
.settings(commonSettings, chiselSettings)
.dependsOn(rocketchip)