2021.9.5 有些地方添加了一點自己的了解!!!
0 前言
這篇講chisel的進階參數化特性。個人感覺chisel相比于verilog優勢可以總結為自頂向下的設計思想替代自底向上的設計思路。從參數化上體驗尤為明顯。進階參數化分為兩部分講,數值的參數化以及拓撲的參數化。
說起參數化,我們希望晶片設計中什麼樣的參數化機制才是好的 ?大緻有以下三個方面。
- 可以變化的參數在頂層暴露,便于統一管理
- 增删參數的時候盡量不動源代碼
- 參數要友善的查找,并盡量保證不會混用
傳統的verilog在參數化的時候逐級傳遞其實一來不利于管理,而來也容易出錯。是以chisel的開發團隊開發出專門的parameter類用于管理較大晶片的參數。
這套機制此處稱之為
site/here/up
連結清單機制,其原理是逐級例化時将參數例化為連結清單,逐級查找。由于scala代碼個人覺得是一種寫起來比較省事但是易讀性差到令人發指的語言,是以直接講原理可能有困難。
是以此處決定采用另一種講法,先講怎麼用,再講原理。先知其然,再知其是以然。
(注:chisel 2.0和3.0這部分其實有小差別的。chisel 2.0将
site/here/up
機制直接嵌入了chisel庫,而3.0将這部分參數化機制分離出來作為單獨的可選檔案,需要引用,本文主要講chisel 3.0)
一、如何使用rocketchip的參數系統
- step0 在你的工程裡加入一個寫好的類庫檔案Config.scala,如下所示:
這樣就可以按照下面的寫法import了。
- step1 先來個祖傳的加法器示範,隻有1個參數—位寬
如5行所示,用
Field[T]
給每個參數擴充出一個名字,Width.
如7~12行,直接繼承
Config
類,構造我們自己的參數管理類
MyConfig
.
其中Config的構造函數是一個
(site,here,up)
元組到
偏函數(Partial Function
,這個詞是部分函數的意思,翻譯成偏函數其實是不準确的)。我們的所有參數就儲存在這個偏函數内。
我這裡為了簡單說明問題,就隻有一個參數。
然後聲明我們的module. 看14行,類的主構造函數的參數用implicit修飾,代表的是隐式參數的意思,也就是如果這個括号裡沒有代入值,則編譯器直接找聲明為implicit val的合适的值。
後續會把我們的參數類的對象代入這個p。取參數也異常簡單。直接用
p(WIDTH)
就能取出來,至于為什麼能取出來,後續介紹。
看25,26行。例化module以後就能正确的産生RTL了。如下。
是不是很簡單?這個時候你就要問了,不是site/up/here機制麼?為什麼不見site/up/here?那是因為我們連結清單目前隻有一層,是以目前的連結清單是這樣的。
二、使用 site/up/here
機制
site/up/here
再複雜一些,變成這樣。16-23行,設計一個新的module, 直接将portb加上一個常數。如上圖所示。ModuleA有一個參數,接收port_width. 同時還接收
Parameters
類型的隐式參數p,該參數用來傳遞配置參數.
在Helloworld Module給ModuleA傳遞參數的時候,使用parameter類的
alterpartial
函數給參數清單傳進去一個新的偏函數。
此時參數連結清單變成了:
那麼在26行查到的WIDTH = 8, 在17行查到的WIDTH = 4,
為什麼是這樣呢?因為在Helloworld裡看到的連結清單是連結清單1,傳給Module A的連結清單是連結清單2.
至此你應該對連結清單的機制有了初步的了解。下面我們就一點一點的修改代碼,以将
site、here和up
都使用上。
1、【here】
現在先說一個最簡單的here。here的意思是在本偏函數内引用。
比如下面情況。
例如我們想要個位寬為16的參數,采用上述寫法。這種寫法在子產品參數有依賴是非常有用。
例如我們此時将ModuleA 中的
width
改成了
p(DBWIDTH)
。
此時會先查找參數最底一層,發現隻有WIDTH和PA,沒有DBWIDTH,是以往上一層查找。找到DHWIDTH=16。生産的verilog如下。
2、【site】
site機制就比較複雜一些,假如我們有一個參數名,例如DBWIDTH,但是在不同的Module裡代表不同的意義,值不一樣,此時就要用到site。此處為了簡單,我們在Helloworld中例化兩個ModuleA。一個叫
m_big_a
, 一個叫
m_small_a
。
為了好了解,此處給出代碼的全文,再做解釋。
如上圖所示。此時生産的RTL如下
此時,
m_big_a 和 m_small_a
子產品看到的參數是不一樣的。兩個子產品看到的參數連結清單如下:
site指的是參數連結清單中最下面那一層。需要注意使用函數添加新的偏函數時,就是在往連結清單底層方向添加,是以在該例中使用
alterpartial
函數新添加的偏函數就是site指向的地方。給ModuleA傳入的是整個連結清單,但是最開始查詢時,是從最底層開始查詢的。
alterpartial
我們以例化
m_big_a
子產品時查詢
DBWIDTH
參數為例介紹查詢過程。共經曆了4個步驟。
由于此處在ModuleA中查詢參數DWIDTH,故site指向的是ModuleA的整個參數連結清單的最底層,也就是在原本參數連結清單的基礎上新添加的一個偏函數,如下所示:
共有4步:
- step 1:在ModuleA的參數表中查找DBWIDTH,發現沒找到。
- step 2:去上一層查找,找到了DBWIDTH,發現調用了
。site(A_TYPE)
- step3:由于site指向的是ModuleA的參數表最底層,也就是新添加的偏函數,是以會查到
。ATYPE = “big_a”
- step 4:根據
查到參數等于6。“big_a”
3、【up】
up機制顯然就是去上一層連結清單查找。
例如将
m_big_a
的例化改成這樣。則連結清單變為了
在ModuleA查詢,此時up是上一層,也就是Helloworld。也是分為4步。
- step1:先在ModuleA中查詢DBWIDTH, 發現有。
- step2:調用up函數
- step3:在上層找到WIDTH
- step4:傳回24
至此,
site/up/here
機制的用法已經都有涉及。但是上面用法介紹無法展示其用途,下面通過一個例子來介紹其用途。
二、典型案例
此處給例子來自《Advanced Parameterization Manual》。如下圖所示的一個典型的CPU架構。代碼寫法是chisel 2.0,與3.0略有差異,但是可以用來介紹用途。
上面的參數化描述如下。加入我們加入一個ecc參數。規定在cache中的memroy要做ecc.則如下描述。
在主子產品中給出Location的偏函數。
在core中location指定的是incore, 在兩個cache中location給定為incache即可。(注:此處為僞代碼,實際上icache和dcache還要在參數表中給出
Cache_type => "i"
或者
Cache_type => "d"
用于查詢ways以及sets.
三、注意事項
下面舉兩個例子,來引出兩個在使用
site/up/here
時需要注意的點。
1、例一
package grammer
import freechips.rocketchip.config.{Config, Field, Parameters}
//這裡沒有提供參數,是為了讓default為None
case object XLEN extends Field[Int](2)
case object Trace extends Field[String](default = "defaultTrace")
case object Nways extends Field[Int](2)
//case object XLEN_2 extends Field[Parameters => Int]
//case object Trace_2 extends Field[Parameters => Boolean]
case object XLEN_2 extends Field[Int](2)
case object Trace_2 extends Field[String](default = "defaultTrace_2")
case object NSets extends Field[Int](2)
class MiniConfig1 extends Config((site, here, up) => {
// Core
case XLEN => 1
case Trace => "Wzx"
case Nways => 16 * site(NSets)
})
class MiniConfig2 extends Config((site, here, up) => {
// Core
case XLEN_2 => 2 * up(XLEN)//4
case Trace_2 => "Alex"
case NSets => 2 * here(XLEN_2)//NSets = 4 * XLEN.default
})
class MiniConfig extends Config(
new MiniConfig2 ++
new MiniConfig1
)
object ConfigTest {
def main(args: Array[String]): Unit = {
def printConfig(implicit p: Parameters) = {
println(s"XLEN = ${p(XLEN)}")
println(s"Trace = ${p(Trace)}")
println(s"Nways = ${p(Nways)}")
println("......")
println(s"XLEN_2 = ${p(XLEN_2)}")
println(s"Trace_2 = ${p(Trace_2)}")
println(s"NSets = ${p(NSets)}")
}
/** ************************************ */
implicit val p: Parameters = new MiniConfig
printConfig(p)
}
}
列印結果:
XLEN = 1
Trace = Wzx
Nways = 128
…
XLEN_2 = 2
Trace_2 = Alex
NSets = 8
需要注意的是
MiniConfig2
中的
NSets
的值,它之是以等于
4 * XLEN.default
,是因為:
一旦使用到了here,那麼後面,注意是後面(這個前後指的是查找順序,而和case分支的順序無關)如果再使用site或者up,仍然會一直查到最後,但是當第一次查到最頂層的一個case分支時就停止,不會再繼續按照該頂層分支的内容繼續查找。最後使用的是該分支的預設值。
2、例二
package grammer2
import freechips.rocketchip.config.{Config, Field, Parameters}
//這裡沒有提供參數,是為了讓default為None
case object XLEN extends Field[Int](2)
case object Trace extends Field[String](default = "defaultTrace")
case object Nways extends Field[Int](2)
case object Test extends Field[Int](2)
case object XLEN_2 extends Field[Int](10)
case object Trace_2 extends Field[String](default = "defaultTrace_2")
case object NSets extends Field[Int](2)
class MiniConfig1 extends Config((site, here, up) => {
// Core
case XLEN => 2
case Trace => "Wzx"
case Nways => 16 * site(NSets)
})
object ConfigTest2 {
def main(args: Array[String]): Unit = {
def printConfig(implicit p: Parameters) = {
println(s"XLEN = ${p(XLEN)}")
println(s"Trace = ${p(Trace)}")
println(s"Nways = ${p(Nways)}")
println("......")
println(s"XLEN_2 = ${p(XLEN_2)}")
println(s"Trace_2 = ${p(Trace_2)}")
println(s"NSets = ${p(NSets)}")
}
/** ************************************ */
val Config1: Parameters = new MiniConfig1
implicit val Config = Config1.alter((site, here, up) =>{
// Core
case XLEN_2 => 2 * up(Nways)
case Trace_2 => "Alex"
case NSets => here(XLEN_2)//2 * Nways.default
case Test => 5
})
printConfig(Config)
}
}
列印結果:
XLEN = 2
Trace = Wzx
Nways = 64
…
XLEN_2 = 64
Trace_2 = Alex
NSets = 4
需要注意的是
XLEN_2
的值,本來以為
XLEN_2 = 32 * NSets
,但其實不是,而是
32*NSets.default
。這是因為:
在使用 site/up/here
時,不能up上去再site下來,否則site的時候直接使用預設值,不會再回到下一層去尋找真實值。注意,這裡指的是不能使用up手動去上層尋找然後再使用site下來找;但是可以在本層找不到時,由該機制本身自動去上一層查找然後再使用site下來找。
四、總結
本篇描述了
site/up/here
的用法,同時舉了一個簡單的例子說明了用途。這一套參數化系統很好的滿足了參數統一、分層管理,不需要層層傳遞的要求。讓系統的參數組織有序化。但
site/up/here
用的好還要了解其原理,關于
site/up/here
的原理可以先參考以下兩篇文章:
- 進階參數化: Site/Here/Up原了解析
- Chisel進階——隐式參數的應用