chisel相比verilog优势之二:高级参数化---Site/Here/Up机制

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机制

在这里插入图片描述

再复杂一些,变成这样。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函数添加新的偏函数时,就是在往链表底层方向添加,所以在该例中使用alterpartial函数新添加的偏函数就是site指向的地方。给ModuleA传入的是整个链表,但是最开始查询时,是从最底层开始查询的。

我们以例化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:根据“big_a”查到参数等于6。

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的原理可以先参考以下两篇文章:

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值