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。

为了理解这个概念,我们举个简单的例子。

在这里插入图片描述

如上面所示,我们构造一个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到时候要混入lazymoduleimp模块中。实现的功能主要是检测是否有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自然灵活度好了好多。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值