• 吃透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())
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这个抽象类就是所有类型的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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接下来,对于所有版本的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()
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这里我们把测试器实现为了一个特质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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    然后是向下计数到-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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    有了这三种实现,我们就可以用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) }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    sbt "testOnly TickerTest"
    
    • 1

    测试结果如下:

    在这里插入图片描述

    全部通过测试。

    结语

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

  • 相关阅读:
    Java开发学习(三)----Bean基础配置及其作用范围
    Bash语法中的字符串拼接与比较
    GPT-4o:重塑人机交互的未来
    java惠购网站计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
    9、Mybatis-Plus 乐观锁
    lecode:239. 滑动窗口最大值-----单调队列解法
    PaddleX解决分类、检测两大场景问题?实战精讲教程来了!
    PHP基础学习第十四篇(了解和使用PHP的数据类型、常量、字符串变量、运算符)
    AlphaFold2源码解析(3)--数据预处理
    SpringBoot整合RocketMQ笔记
  • 原文地址:https://blog.csdn.net/weixin_43681766/article/details/126177024