2021.9.5 有些地方添加了一点自己的理解!!!
0 绪论前面已经介绍了chisel的初级和高级参数化。
如何把这些东西有效的在系统中组织起来呢?如何在系统中快捷的使用他们?这篇文章主要解决这个问题。主要涉及到几个东西,一一介绍吧。
- 原理:trait和cake pattern
- 原理:参数的++级联
- 应用:使用trait和cake pattern构造模块,使用++级联参数作为trait的开关
注意的是此处介绍的应用形式只是trait pattern中rocket chip推荐的一种参数组织办法,实际上还有很多其他组织方式。
一、什么是trait和cake pattern?trait是scala的一个概念,特质。之所以搞出trait主要是为了多重继承。scala中extends后面只能接一个类,其他的就需要使用with trait来操作。
直观上理解的话,module a with trait_b可以说成在b中混入了特质b。
为了理解这个概念,我们举个简单的例子。
如上面所示,我们构造一个Cat类。然后做了两个特质HasLeg, HasMouth.
我们先给cat_a混入HasLeg类,此时猫就可以到处跑。再在cat_b混入HasMouth, 此时猫就可以叫。运行结果如下。
这个例子虽然简单,但是可以说明trait的用法,以及cake pattern是什么。
cake pattern在此处指的是像蛋糕一样,一层一层的包裹trait。这是蛋糕大致的样子。
至此,trait和cake pattern的概念应该搞清楚了。
二、参数级联是什么?参数级联,我们在RocketChip中经常看到Config构造函数一堆级联,比如这样。
class BaseConfig extends Config(
new WithDefaultMemPort() ++
new WithDefaultMMIOPort() ++
new WithDefaultSlavePort() ++
new BaseSubsystemConfig()
)
这个地方主要是弄懂++是什么意思。其实++是parameters里重载的符号。
其效果其实就是把这些config组成了一个参数链。和使用alter一个原理。
至此,参数级联的原理已经说清楚了。接下来我们讲如何利用trait的cake pattern和参数级联实现对芯片系统的参数化。
三、芯片系统的参数化此处我们以一个给CPU系统添加求最大公约数外设GCD的案例来说明如何参数化系统。我们直接用chipyard的官方例子来解析,原始案例见下面网站。
Keys, Traits, and Configs
我们实现一个这样子的设计。
如上图所示
- 系统带不带GCD这个外设可配置
- 如果带GCD这个外设,需要在顶层加一个busy端口
- GCD又有两种规格(TL和AXI)的实现可参数化配置选用
下面我们来实现这个设计。
首先给我们的模块定义需要的参数。
case class GCDParams(
address: BigInt = 0x2000,
width: Int = 32,
useAXI4: Boolean = false,
useBlackBox: Boolean = true)
然后构造GCD特性。构造的特性分为连接关系的特性与功能实现的特性。
- 首先来看连接的特性
trait CanHavePeripheryGCD { this: BaseSubsystem =>
private val portName = "gcd"
// Only build if we are using the TL (nonAXI4) version
val gcd = p(GCDKey) match {
case Some(params) => {
if (params.useAXI4) {
val gcd = LazyModule(new GCDAXI4(params, pbus.beatBytes)(p))
pbus.toSlave(Some(portName)) {
gcd.node :=
AXI4Buffer () :=
TLToAXI4 () :=
// toVariableWidthSlave doesn't use holdFirstDeny, which TLToAXI4() needsx
TLFragmenter(pbus.beatBytes, pbus.blockBytes, holdFirstDeny = true)
}
Some(gcd)
} else {
val gcd = LazyModule(new GCDTL(params, pbus.beatBytes)(p))
pbus.toVariableWidthSlave(Some(portName)) { gcd.node }
Some(gcd)
}
}
case None => None
}
}
如上图所示,该trait叫CanHavePeripheryGCD
,之所以叫Can, 就是说这个地方只是一种能力。具体是不是例化这个外设根据参数来定。
我们分析一下,首先通过读取参数, 来确定是不是实现这个模块。
val gcd = p(GCDKey) match{
......
}
如果是case Some(params),
说明参数链中有这个参数,可以构造参数。
然后看是不是AXI4版本的。我们以AXI4版本为例来说明。
接下来就执行括号内链接的构造。
val gcd = LazyModule(new GCDAXI4(params, pbus.beatBytes)(p))
pbus.toSlave(Some(portName)) {
gcd.node :=
AXI4Buffer () :=
TLToAXI4 () :=
// toVariableWidthSlave doesn't use holdFirstDeny, which TLToAXI4() needsx
TLFragmenter(pbus.beatBytes, pbus.blockBytes, holdFirstDeny = true)
}
先构造出连接在AXI4总线的GCDAXI4
模块, 然后通过pbus.toSlave
这个函数将gcd的node连接到总线node上。从此就构造出了这个module和连接。TL(tilelink总线,rocketchip提出的总线)版本的同理。
- 再来看功能实现的特性
由于加上了GCD模块,我们可能会需要在顶层多加一个端口,那么就在实现层面再加一个特性,到时候在实现层面混入该特性即可。
// DOC include start: GCD imp trait
trait CanHavePeripheryGCDModuleImp extends LazyModuleImp {
val outer: CanHavePeripheryGCD
val gcd_busy = outer.gcd match {
case Some(gcd) => {
val busy = IO(Output(Bool()))
busy := gcd.module.io.gcd_busy
Some(busy)
}
case None => None
}
}
如上面一端代码。这个trait到时候要混入lazymodule
的imp
模块中。实现的功能主要是检测是否有gcd module, 如果有gcd module就加一个输出端口叫busy
。
我们需要的两个能力特性就构造好了。接下来把他们混入我们原有的设计。
如上图所示,直接用with把具有连接特性的trait混入DigitalTop中。
然后在具体的module实现中也通过with把具有功能特性的ModuleImp trait 混入到DigitalTopModule中。
注意,每一个trait Canxxx
一般都对应一个trait CanxxxModuleImp
,它们是成对出现的。前者是实现连接的特性,后者是实现功能(比如添加端口)的特性。
至此,我们就在我们的系统中添加了新的配置GCD功能的特性。
那么到底这个GCD怎么参数化的配置有没有以及类型呢?
- 最后还需要构造一个
site up here
类型的config
class WithGCD(useAXI4: Boolean, useBlackBox: Boolean) extends Config((site, here, up) => {
case GCDKey => Some(GCDParams(useAXI4 = useAXI4, useBlackBox = useBlackBox))
})
如上面所示,构造一个withGCD
的config备用。注意,这里返回的刚好是Some(GCDParams),是一个可选值类型,对应CanHavePeripheryGCD
特质中的:
case Some(params) => {}
根据模式匹配的知识,此时的params
就是GCDParams
,所以我们在下面才可以使用params.useAXI4
,这是因为GCDParams
这个样例类中包含useAXI4
这个属性。
实际使用的时候,如果需要这个GCD模块,我们直接用前面讲到的config链条++将该config链接到配置链上即可,如下图所示。
只要链条上有这个配置,前述trait CanHavePeripheryGCD
中的p(GCDKey)
就会返回Some(GCDParams(useAXI4 = useAXI4, useBlackBox = useBlackBox))
,这样我们就可以使用返回的参数构造链接,将我们的module连接上。实际上一个系统中有非常多类似GCD这种可配置模块。
至此,chisel的参数化原理基本上讲全了,后续再补充其他内容。本节讲了chisel如何组织系统进行快速参数化,从系统架构层面对芯片设计进行参数化。给我个人的感觉,chisel之所以相比于verilog能提供更好的参数化与更好的表达效率,主要原因是我们以一个写verilog构造器的思维在写chisel, 可构造的verilog自然灵活度好了好多。