天天看點

chisel(Rocket Chip)中如何參數化晶片系統

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。

為了了解這個概念,我們舉個簡單的例子。

chisel(Rocket Chip)中如何參數化晶片系統

如上面所示,我們構造一個Cat類。然後做了兩個特質HasLeg, HasMouth.

我們先給cat_a混入HasLeg類,此時貓就可以到處跑。再在cat_b混入HasMouth, 此時貓就可以叫。運作結果如下。

chisel(Rocket Chip)中如何參數化晶片系統

這個例子雖然簡單,但是可以說明trait的用法,以及cake pattern是什麼。

cake pattern在此處指的是像蛋糕一樣,一層一層的包裹trait。這是蛋糕大緻的樣子。

chisel(Rocket Chip)中如何參數化晶片系統

至此,trait和cake pattern的概念應該搞清楚了。

二、參數級聯是什麼?

參數級聯,我們在RocketChip中經常看到Config構造函數一堆級聯,比如這樣。

class BaseConfig extends Config(
	new WithDefaultMemPort() ++
	new WithDefaultMMIOPort() ++
	new WithDefaultSlavePort() ++
	new BaseSubsystemConfig()
)
           

這個地方主要是弄懂++是什麼意思。其實++是parameters裡重載的符号。

chisel(Rocket Chip)中如何參數化晶片系統

其效果其實就是把這些config組成了一個參數鍊。和使用alter一個原理。

chisel(Rocket Chip)中如何參數化晶片系統
chisel(Rocket Chip)中如何參數化晶片系統

至此,參數級聯的原理已經說清楚了。接下來我們講如何利用trait的cake pattern和參數級聯實作對晶片系統的參數化。

三、晶片系統的參數化

此處我們以一個給CPU系統添加求最大公約數外設GCD的案例來說明如何參數化系統。我們直接用chipyard的官方例子來解析,原始案例見下面網站。

Keys, Traits, and Configs

我們實作一個這樣子的設計。

chisel(Rocket Chip)中如何參數化晶片系統

如上圖所示

  • 系統帶不帶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

我們需要的兩個能力特性就構造好了。接下來把他們混入我們原有的設計。

chisel(Rocket Chip)中如何參數化晶片系統

如上圖所示,直接用with把具有連接配接特性的trait混入DigitalTop中。

然後在具體的module實作中也通過with把具有功能特性的ModuleImp trait 混入到DigitalTopModule中。

注意,每一個

trait Canxxx

一般都對應一個

trait CanxxxModuleImp

,它們是成對出現的。前者是實作連接配接的特性,後者是實作功能(比如添加端口)的特性。

chisel(Rocket Chip)中如何參數化晶片系統

至此,我們就在我們的系統中添加了新的配置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連結到配置鍊上即可,如下圖所示。

chisel(Rocket Chip)中如何參數化晶片系統

隻要鍊條上有這個配置,前述trait

CanHavePeripheryGCD

中的

p(GCDKey)

就會傳回

Some(GCDParams(useAXI4 = useAXI4, useBlackBox = useBlackBox))

,這樣我們就可以使用傳回的參數構造連結,将我們的module連接配接上。實際上一個系統中有非常多類似GCD這種可配置子產品。

四、總結

至此,chisel的參數化原理基本上講全了,後續再補充其他内容。本節講了chisel如何組織系統進行快速參數化,從系統架構層面對晶片設計進行參數化。給我個人的感覺,chisel之是以相比于verilog能提供更好的參數化與更好的表達效率,主要原因是我們以一個寫verilog構造器的思維在寫chisel, 可構造的verilog自然靈活度好了好多。

繼續閱讀