吃透Chisel语言.34.Chisel进阶之硬件生成器(三)——利用面向对象编程特性:以Ticker为例

Chisel进阶之硬件生成器(三)——利用面向对象编程特性:以Ticker为例

上一篇文章中,我们利用了Scala中的IO操作、循环以及序列上的map函数轻松实现了Chisel逻辑表的生成,效率远高于用Verilog或VHDL和其他脚本语言配合使用。不过Scala作为一种高级编程语言,其面向对象编程和函数式编程特性会为开发带来更多便利。Chisel自然也继承了Scala的语言特性,接下来的两篇文章将分别讲解如何利用Chisel的面向对象编程特性和函数式编程特性,这一篇文章从面向对象特性开始。

从自定义抽象类Ticker继承

Chisel是从Scala拓展得到的,因此也是一个面向对象的语言。作为硬件组件,Chisel的Module也是Scala的一个类。因此,我们可以用继承的思想来把一个共同的行为归为一个父类,下面我们就试试如何利用继承。

前面的例子中,我们学习了几种不同类型的计数器,它们可以用于低频tick的生成。我们假设想要尝试不同的版本,比如比较它们对资源的需求。我们从一个抽象类开始定义接口:

abstract class Ticker(n: Int) extends Module {
    val io = IO(new Bundle{
        val tick = Output(Bool())
    })
}

这个抽象类就是所有类型的Ticker的共同特点的提炼,因为他们的io接口都是一样的。下面我们就可以用这个抽象类来实现一个带一个计数器、向上计数、用于tick生成的Ticker了:

class UpTicker(n: Int) extends Ticker(n) {
    val N = (n-1).U
    
    val cntReg = RegInit(0.U(8.W))
    
    cntReg := cntReg + 1.U
    when(cntReg === N) {
        cntReg := 0.U
    }
    
    io.tick := cntReg === N
}

接下来,对于所有版本的Ticker逻辑我们都可以用同一个testbench进行测试。我们只需要定义接受Ticker的子类型的testbench就行了。这个TickerTester应该有这么几个参数:

  1. 类型参数[T <: Ticker],用于接受一个Ticker类或从Ticker继承的类;
  2. 被测试的设计,为类型T或其子类型;
  3. 对于每个tick我们期望的时钟数。

测试器会等待第一个tick的发生(开始的时间可能和其他实现有所不同),然后检查tick是否每n个时钟周期发生一次。测试器的实现代码如下:

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

trait TickerTestFunc {
    def testFn[T <: Ticker](dut: T, n: Int) = {
        // -1表示还没得到任何tick
        var count = -1
        for (_ <- 0 to n * 3) {
            // 检查输出是否正确
            if (count > 0)
            	dut.io.tick.expect(false.B)
            else if (count == 0)
            	dut.io.tick.expect(true.B)
        
            // 在每个tick上重置计数器
            if (dut.io.tick.peek.litToBoolean)
                count = n-1
            else
                count -= 1
            dut.clock.step()
        }
    }
}

这里我们把测试器实现为了一个特质trait,我们可以在testbench上with这个特质就能用它了。

在实现测试器的过程中,首先,我们可以测试测试器本身,比如使用一些println作为调试手段。如果我们对这个Ticker比较有信心了,并且测试器也没问题,那我们就可以继续实现其他Ticker了。下面是向下计数的Ticker的Chisel实现:

class DownTicker(n: Int) extends Ticker(n) {
    val N = (n-1).U
    
    val cntReg = RegInit(N)
    
    cntReg := cntReg - 1.U
    when(cntReg === 0.U) {
        cntReg := N
    }
    
    io.tick := cntReg === N
}

然后是向下计数到-1并通过避免使用比较器因此消耗硬件更少的优化版本:

class NerdTicker(n: Int) extends Ticker(n) {
    val N = n
    val MAX = (N-2).S(8.W)
    val cntReg = RegInit(MAX)
    
    io.tick := false.B
    
    cntReg := cntReg - 1.S
    when(cntReg(7)) {
        cntReg := MAX
        io.tick := true.B
    }
}

有了这三种实现,我们就可以用ScalaTest规范来测试了,分别为不同版本的实现创建一个实例,然后把它们传递给测试函数,测试部分实现如下:

class TickerTest extends AnyFlatSpec with ChiselScalatestTester with TickerTestFunc {
    "UpTicker 5" should "pass" in {
        test(new UpTicker(5)) { dut => testFn(dut, 5) }
    }
    "DownTicker 7" should "pass" in {
        test(new DownTicker(7)) { dut => testFn(dut, 7) }
    }
    "NerdTicker 11" should "pass" in {
        test(new NerdTicker(11)) { dut => testFn(dut, 11) }
    }
}

最后,我们直接运行测试就行了:

sbt "testOnly TickerTest"

测试结果如下:

在这里插入图片描述

全部通过测试。

结语

这一篇文章我们提取了之前实现的几种tick生成模块的共同特征,实现了一个抽象类Ticker。对于这个Ticker,我们通过继承的方法实现了几种不同的版本,还可以用统一的测试接口对这些版本进行测试,整个代码清晰了很多,对于实现和测试成本也小了很多。此外,我们针对不同版本的实现也构建了统一的测试器函数,以trait的形式with到testbench中就可以调用,非常方便。Scala作为支持函数式编程的语言,Chisel自然也可以利用函数式编程的特性,下一篇文章我们就共同学习利用函数式编程的硬件生成。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值