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
特質中的:
根據模式比對的知識,此時的
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自然靈活度好了好多。