Chisel 测试模块

一个普通的测试模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class FlipFlopTest extends AnyFlatSpec with ChiselScalatestTester {
"FlipFlop" should "correctly store the input on the rising edge of the clock" in {
test(new FlipFlop) { c =>
// 初始状态
c.io.d.poke(false.B) // 设置输入 d
c.clock.step(1) // 推进时钟
c.io.q.expect(false.B) // 检查输出 q

// 设置 D 为 true
c.io.d.poke(true.B)
c.clock.step(1)
c.io.q.expect(true.B)

// 设置 D 为 false
c.io.d.poke(false.B)
c.clock.step(1)
c.io.q.expect(false.B)
}
}
}

注意到,test()是一个函数调用(并不是函数定义),它的第二个参数有点奇怪,是的,test是一个高阶函数,它第二个参数还是一个匿名函数。

当然,Scala 中的这种语法确实非常强大且灵活。test(new MyOperators) { c => ... } 的用法利用了 Scala 的高阶函数和匿名函数的特性。

1. Scala 中的高阶函数和匿名函数

高阶函数(Higher-Order Functions) 是指接受函数作为参数,或者返回一个函数的函数。匿名函数(Lambda 表达式) 是没有名称的函数,可以直接在需要的地方定义和使用。

在 Scala 中,当一个方法的最后一个参数是函数时,你可以使用大括号 {} 来传递这个函数,而不是使用圆括号 ()。这使得代码更加简洁和可读,特别是在编写复杂的函数调用时。

2. 基本示例

让我们从一些简单的例子开始,展示如何在 Scala 中使用这种语法。

2.1 使用 foreach 遍历集合

1
2
3
4
5
6
7
8
9
val numbers = List(1, 2, 3, 4, 5)

// 使用圆括号传递匿名函数
numbers.foreach(x => println(x))

// 使用大括号传递匿名函数
numbers.foreach { x =>
println(x)
}

解释:

  • foreach 是一个高阶函数,它接受一个函数作为参数。
  • 匿名函数 x => println(x) 被传递给 foreach
  • 两种语法的效果相同,使用大括号的语法在代码块较大时更加清晰。

2.2 使用 map 转换集合

1
2
3
4
5
6
7
8
9
10
11
12
val numbers = List(1, 2, 3, 4, 5)

// 使用圆括号传递匿名函数
val doubled = numbers.map(x => x * 2)

// 使用大括号传递匿名函数
val tripled = numbers.map { x =>
x * 3
}

println(doubled) // 输出: List(2, 4, 6, 8, 10)
println(tripled) // 输出: List(3, 6, 9, 12, 15)

解释:

  • map 是一个高阶函数,用于将集合中的每个元素转换为新的元素。
  • 匿名函数 x => x * 2{ x => x * 3 } 分别将每个元素乘以 2 和 3。

3. 自定义高阶函数示例

让我们创建一个自定义的高阶函数,进一步理解这种语法的使用。

3.1 定义一个高阶函数

1
2
3
def applyTwice(f: Int => Int, x: Int): Int = {
f(f(x))
}

解释:

  • applyTwice 接受一个函数 f 和一个整数 x,并将 f 应用两次于 x

3.2 使用 applyTwice 传递匿名函数

1
2
3
4
5
6
7
val result1 = applyTwice((x: Int) => x + 3, 7) // 输出: 13
val result2 = applyTwice { x =>
x * 2
} (5) // 输出: 20

println(result1) // 输出: 13
println(result2) // 输出: 20

解释:

  • 第一个调用使用圆括号传递匿名函数 x => x + 3
  • 第二个调用使用大括号传递匿名函数 x => x * 2
  • 两种语法在功能上是等价的。

4. 多参数列表与大括号语法

Scala 允许方法有多个参数列表(curried functions)。当方法的最后一个参数列表是函数时,可以使用大括号 {} 来传递该函数。

4.1 定义一个多参数列表的方法

1
2
3
def greet(name: String)(formatter: String => String): String = {
formatter(s"Hello, $name!")
}

解释:

  • greet 方法有两个参数列表,第一个是 name,第二个是一个函数 formatter

4.2 调用 greet 方法

1
2
3
4
5
6
7
8
val result1 = greet("Alice") { name =>
name.toUpperCase
}

val result2 = greet("Bob")(name => name.reverse)

println(result1) // 输出: HELLO, ALICE!
println(result2) // 输出: !boB ,olleH

解释:

  • 在调用 greet("Alice") { name => name.toUpperCase } 时,第二个参数列表中的匿名函数被传递在大括号内。
  • 这种语法特别适用于 DSL(领域特定语言)和配置块,使代码更加易读。

5. 更复杂的示例:ChiselTest 中的 test 方法

回到你在 ChiselTest 中遇到的例子,test(new MyOperators) { c => ... } 正是利用了上述的 Scala 语法特性。

5.1 分析 test 方法

假设 test 方法的定义如下:

1
2
3
4
def test[T <: Module](m: T)(testFn: T => Unit): Unit = {
// 初始化仿真环境
testFn(m)
}

解释:

  • test 方法有两个参数列表,第一个是模块实例 m,第二个是一个函数 testFn,它接受模块实例并执行测试逻辑。
  • 当调用 test(new MyOperators) { c => ... } 时,{ c => ... } 是传递给 testFn 的匿名函数。

5.2 示例调用

1
2
3
4
5
test(new MyOperators) { c =>
// 测试逻辑
c.io.in_a.poke(3.U)
c.io.out.expect(9.U)
}

解释:

  • new MyOperators 创建了一个模块实例。
  • { c => ... } 定义了如何测试这个模块实例,其中 cMyOperators 的实例。

6. 更多例子

6.1 使用 foldLeft

1
2
3
4
5
6
val numbers = List(1, 2, 3, 4, 5)
val sum = numbers.foldLeft(0) { (acc, x) =>
acc + x
}

println(sum) // 输出: 15

解释:

  • foldLeft 是一个高阶函数,它接受一个初始值和一个累加函数。
  • 匿名函数 (acc, x) => acc + x 被传递在大括号中,用于累加列表中的所有元素。

6.2 使用 filter

1
2
3
4
5
6
val numbers = List(1, 2, 3, 4, 5, 6)
val evenNumbers = numbers.filter { x =>
x % 2 == 0
}

println(evenNumbers) // 输出: List(2, 4, 6)

解释:

  • filter 是一个高阶函数,它接受一个返回布尔值的函数,用于筛选符合条件的元素。
  • 匿名函数 { x => x % 2 == 0 } 被传递在大括号中,用于筛选偶数。

6.3 使用自定义方法

1
2
3
4
5
6
7
8
9
10
11
12
def executeTwice(action: () => Unit): Unit = {
action()
action()
}

executeTwice { () =>
println("Action executed")
}

// 输出:
// Action executed
// Action executed

解释:

  • executeTwice 是一个高阶函数,它接受一个无参数且返回 Unit 的函数 action
  • 匿名函数 { () => println("Action executed") } 被传递在大括号中,并执行两次。

7. 小结

Scala 中的方法可以接受多个参数列表,特别是当最后一个参数列表是函数时,可以使用大括号 {} 来传递匿名函数。这种语法使得代码更加简洁和易读,特别适用于需要传递代码块或回调函数的场景,如集合操作、高阶函数、测试框架等。

在 ChiselTest 中看到的 test(new MyOperators) { c => ... } 正是利用了这种语法特性,通过传递一个匿名函数来定义测试逻辑。


Chisel 测试模块
http://blog.luliang.online/2024/09/19/Chisel的测试模块/
作者
Luyoung
发布于
2024年9月19日
许可协议