Skip to content

Commit 17c9e97

Browse files
Standardize inline testharness interface to clock, reset, finish, and success (#4832)
1 parent 1b4df2d commit 17c9e97

File tree

2 files changed

+113
-89
lines changed

2 files changed

+113
-89
lines changed

src/main/scala/chisel3/experimental/inlinetest/InlineTest.scala

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,53 +11,83 @@ import chisel3.experimental.hierarchy.{Definition, Instance}
1111
* @tparam M the type of the DUT module
1212
* @tparam R the type of the result returned by the test body
1313
*/
14-
class TestParameters[M <: RawModule, R] private[inlinetest] (
14+
final class TestParameters[M <: RawModule, R] private[inlinetest] (
1515
/** The [[desiredName]] of the DUT module. */
1616
val dutName: String,
1717
/** The user-provided name of the test. */
1818
val testName: String,
1919
/** A Definition of the DUT module. */
2020
val dutDefinition: Definition[M],
2121
/** The body for this test, returns a result. */
22-
val testBody: Instance[M] => R,
22+
private[inlinetest] val testBody: Instance[M] => R,
2323
/** The reset type of the DUT module. */
24-
val resetType: Option[Module.ResetType.Type]
24+
private[inlinetest] val dutResetType: Option[Module.ResetType.Type]
2525
) {
26-
final def desiredTestModuleName = s"test_${dutName}_${testName}"
26+
27+
/** The concrete reset type of the testharness module. */
28+
private[inlinetest] final def testHarnessResetType = dutResetType match {
29+
case Some(rt @ Module.ResetType.Synchronous) => rt
30+
case Some(rt @ Module.ResetType.Asynchronous) => rt
31+
case _ => Module.ResetType.Synchronous
32+
}
33+
34+
/** The [[desiredName]] for the testharness module. */
35+
private[inlinetest] final def testHarnessDesiredName = s"test_${dutName}_${testName}"
2736
}
2837

29-
/** Contains traits that implement behavior common to generators for unit test testharness modules. */
30-
object TestHarness {
31-
import chisel3.{Module => ChiselModule, RawModule => ChiselRawModule}
38+
sealed class TestResultBundle extends Bundle {
3239

33-
/** TestHarnesses for inline tests without clock and reset IOs should extend this. This
34-
* trait sets the correct desiredName for the module, instantiates the DUT, and provides
35-
* methods to elaborate the test.
36-
*
37-
* @tparam M the type of the DUT module
38-
* @tparam R the type of the result returned by the test body
39-
*/
40-
trait RawModule[M <: ChiselRawModule, R] extends Public { this: ChiselRawModule =>
41-
def test: TestParameters[M, R]
42-
override def desiredName = test.desiredTestModuleName
43-
val dut = Instance(test.dutDefinition)
44-
final def elaborateTest(): R = test.testBody(dut)
45-
}
40+
/** The test shall be considered complete on the first positive edge of
41+
* [[finish]] by the simulation. The [[TestHarness]] must drive this.
42+
*/
43+
val finish = Bool()
44+
45+
/** The test shall pass if this is asserted when the test is complete.
46+
* The [[TestHarness]] must drive this.
47+
*/
48+
val success = Bool()
49+
}
4650

47-
/** TestHarnesses for inline tests should extend this. This trait sets the correct desiredName for
48-
* the module, instantiates the DUT, and provides methods to elaborate the test. By default, the
49-
* reset is synchronous, but this can be changed by overriding [[resetType]].
51+
/** TestHarnesses for inline tests should extend this. This abstract class sets the correct desiredName for
52+
* the module, instantiates the DUT, and provides methods to generate the test. The [[resetType]] matches
53+
* that of the DUT, or is [[Synchronous]] if it must be inferred (this can be overriden).
54+
*
55+
* A [[TestHarness]] has the following ports:
56+
*
57+
* - [[clock]]: shall be driven at a constant frequency by the simulation.
58+
* - [[reset]]: shall be asserted for one cycle from the first positive edge of [[clock]] by the simulation.
59+
* - [[reset]]: shall be asserted for one cycle from the first positive edge of [[clock]] by the simulation.
60+
* - [[finish]]: the test shall be considered complete on the first positive edge of [[finish]].
5061
*
5162
* @tparam M the type of the DUT module
5263
* @tparam R the type of the result returned by the test body
5364
*/
54-
trait Module[M <: ChiselRawModule, R] extends RawModule[M, R] { this: ChiselModule =>
55-
override def resetType = test.resetType match {
56-
case Some(rt @ Module.ResetType.Synchronous) => rt
57-
case Some(rt @ Module.ResetType.Asynchronous) => rt
58-
case _ => Module.ResetType.Synchronous
59-
}
60-
}
65+
abstract class TestHarness[M <: RawModule, R](test: TestParameters[M, R])
66+
extends FixedIOModule(new TestResultBundle)
67+
with Public {
68+
override final def desiredName = test.testHarnessDesiredName
69+
override final def resetType = test.testHarnessResetType
70+
71+
// Handle the base case where a test has no result. In this case, we expect
72+
// the test to end the simulation and signal pass/fail.
73+
io.finish := false.B
74+
io.success := true.B
75+
76+
protected final val dut = Instance(test.dutDefinition)
77+
protected final val testResult = test.testBody(dut)
78+
}
79+
80+
/** TestHarnesses for inline tests should extend this. This abstract class sets the correct desiredName for
81+
* the module, instantiates the DUT, and provides methods to generate the test. The [[resetType]] matches
82+
* that of the DUT, or is [[Synchronous]] if it must be inferred (this can be overriden).
83+
*
84+
* @tparam M the type of the DUT module
85+
* @tparam R the type of the result returned by the test body
86+
*/
87+
abstract class TestHarnessWithResult[M <: RawModule](test: TestParameters[M, TestResultBundle])
88+
extends TestHarness[M, TestResultBundle](test) {
89+
io.finish := testResult.finish
90+
io.success := testResult.success
6191
}
6292

6393
/** An implementation of a testharness generator. This is a type class that defines how to
@@ -69,22 +99,25 @@ object TestHarness {
6999
trait TestHarnessGenerator[M <: RawModule, R] {
70100

71101
/** Generate a testharness module given the test parameters. */
72-
def generate(test: TestParameters[M, R]): RawModule with Public
102+
def generate(test: TestParameters[M, R]): TestHarness[M, R]
73103
}
74104

75105
object TestHarnessGenerator {
76106

77-
/** The minimal implementation of a unit testharness. Has a clock input and a synchronous reset
78-
* input. Connects these to the DUT and does nothing else.
79-
*/
80-
class UnitTestHarness[M <: RawModule](val test: TestParameters[M, Unit])
81-
extends Module
82-
with TestHarness.Module[M, Unit] {
83-
elaborateTest()
107+
/** Factory for a TestHarnessGenerator typeclass. */
108+
def apply[M <: RawModule, R](gen: TestParameters[M, R] => TestHarness[M, R]) =
109+
new TestHarnessGenerator[M, R] {
110+
override def generate(test: TestParameters[M, R]) = gen(test)
111+
}
112+
113+
/** Provides a default testharness for tests that return [[Unit]]. */
114+
implicit def baseTestHarnessGenerator[M <: RawModule]: TestHarnessGenerator[M, Unit] = {
115+
TestHarnessGenerator(new TestHarness[M, Unit](_) {})
84116
}
85117

86-
implicit def unitTestHarness[M <: RawModule]: TestHarnessGenerator[M, Unit] = new TestHarnessGenerator[M, Unit] {
87-
override def generate(test: TestParameters[M, Unit]) = new UnitTestHarness(test)
118+
/** Provides a default testharness for tests that return a [[TestResultBundle]] */
119+
implicit def resultTestHarnessGenerator[M <: RawModule]: TestHarnessGenerator[M, TestResultBundle] = {
120+
TestHarnessGenerator(new TestHarnessWithResult[M](_) {})
88121
}
89122
}
90123

@@ -94,7 +127,7 @@ object TestHarnessGenerator {
94127
* hardware indicating completion and/or exit code to the testharness.
95128
*/
96129
trait HasTests { module: RawModule =>
97-
type M = module.type
130+
private type M = module.type
98131

99132
/** Whether inline tests will be elaborated as a top-level definition to the circuit. */
100133
protected def elaborateTests: Boolean = true

src/test/scala/chiselTests/experimental/InlineTestSpec.scala

Lines changed: 39 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,40 +12,17 @@ import org.scalatest.matchers.should.Matchers
1212

1313
import circt.stage.ChiselStage.emitCHIRRTL
1414

15-
class TestResultBundle extends Bundle {
16-
val finish = Output(Bool())
17-
val code = Output(UInt(8.W))
18-
}
19-
20-
// Here is a testharness that consumes some kind of hardware from the test body, e.g.
21-
// a finish and pass/fail interface.
22-
object TestHarnessWithResultIO {
23-
class TestHarnessWithResultIOModule[M <: RawModule](val test: TestParameters[M, TestResultBundle])
24-
extends Module
25-
with TestHarness.Module[M, TestResultBundle] {
26-
val result = IO(new TestResultBundle)
27-
result := elaborateTest()
28-
}
29-
implicit def testharnessGenerator[M <: RawModule]: TestHarnessGenerator[M, TestResultBundle] =
30-
new TestHarnessGenerator[M, TestResultBundle] {
31-
def generate(test: TestParameters[M, TestResultBundle]) = new TestHarnessWithResultIOModule(test)
32-
}
15+
// Here is a testharness that expects some sort of interface on its DUT, e.g. a probe
16+
// socket to which to attach a monitor.
17+
class TestHarnessWithMonitorSocket[M <: RawModule with HasMonitorSocket](test: TestParameters[M, Unit])
18+
extends TestHarness[M, Unit](test) {
19+
val monitor = Module(new ProtocolMonitor(dut.monProbe.cloneType))
20+
monitor.io :#= probe.read(dut.monProbe)
3321
}
3422

3523
object TestHarnessWithMonitorSocket {
36-
// Here is a testharness that expects some sort of interface on its DUT, e.g. a probe
37-
// socket to which to attach a monitor.
38-
class TestHarnessWithMonitorSocketModule[M <: RawModule with HasMonitorSocket](val test: TestParameters[M, Unit])
39-
extends Module
40-
with TestHarness.Module[M, Unit] {
41-
val monitor = Module(new ProtocolMonitor(dut.monProbe.cloneType))
42-
monitor.io :#= probe.read(dut.monProbe)
43-
elaborateTest()
44-
}
45-
implicit def testharnessGenerator[M <: RawModule with HasMonitorSocket]: TestHarnessGenerator[M, Unit] =
46-
new TestHarnessGenerator[M, Unit] {
47-
def generate(test: TestParameters[M, Unit]): RawModule with Public = new TestHarnessWithMonitorSocketModule(test)
48-
}
24+
implicit def testharnessGenerator[M <: RawModule with HasMonitorSocket] =
25+
TestHarnessGenerator[M, Unit](new TestHarnessWithMonitorSocket(_))
4926
}
5027

5128
@instantiable
@@ -107,23 +84,20 @@ class ModuleWithTests(
10784
assert(instance.io.out =/= 0.U): Unit
10885
}
10986

110-
{
111-
import TestHarnessWithResultIO._
112-
test("with_result") { instance =>
113-
val result = Wire(new TestResultBundle)
114-
val timer = RegInit(0.U)
115-
timer := timer + 1.U
116-
instance.io.in := 5.U(ioWidth.W)
117-
val outValid = instance.io.out =/= 0.U
118-
when(outValid) {
119-
result.code := 0.U
120-
result.finish := timer > 1000.U
121-
}.otherwise {
122-
result.code := 1.U
123-
result.finish := true.B
124-
}
125-
result
87+
test("with_result") { instance =>
88+
val result = Wire(new TestResultBundle)
89+
val timer = RegInit(0.U)
90+
timer := timer + 1.U
91+
instance.io.in := 5.U(ioWidth.W)
92+
val outValid = instance.io.out =/= 0.U
93+
when(outValid) {
94+
result.success := 0.U
95+
result.finish := timer > 1000.U
96+
}.otherwise {
97+
result.success := 1.U
98+
result.finish := true.B
12699
}
100+
result
127101
}
128102

129103
{
@@ -173,22 +147,29 @@ class InlineTestSpec extends AnyFlatSpec with FileCheck {
173147
| CHECK: public module test_ModuleWithTests_foo
174148
| CHECK-NEXT: input clock : Clock
175149
| CHECK-NEXT: input reset
150+
| CHECK-NEXT: output finish : UInt<1>
151+
| CHECK-NEXT: output success : UInt<1>
176152
| CHECK: inst dut of ModuleWithTests
177153
|
178154
| CHECK: public module test_ModuleWithTests_bar
179155
| CHECK-NEXT: input clock : Clock
180156
| CHECK-NEXT: input reset
157+
| CHECK-NEXT: output finish : UInt<1>
158+
| CHECK-NEXT: output success : UInt<1>
181159
| CHECK: inst dut of ModuleWithTests
182160
|
183161
| CHECK: public module test_ModuleWithTests_with_result
184162
| CHECK-NEXT: input clock : Clock
185163
| CHECK-NEXT: input reset
186-
| CHECK-NEXT: output result : { finish : UInt<1>, code : UInt<8>}
164+
| CHECK-NEXT: output finish : UInt<1>
165+
| CHECK-NEXT: output success : UInt<1>
187166
| CHECK: inst dut of ModuleWithTests
188167
|
189168
| CHECK: public module test_ModuleWithTests_with_monitor
190169
| CHECK-NEXT: input clock : Clock
191170
| CHECK-NEXT: input reset
171+
| CHECK-NEXT: output finish : UInt<1>
172+
| CHECK-NEXT: output success : UInt<1>
192173
| CHECK: inst dut of ModuleWithTests
193174
| CHECK: inst monitor of ProtocolMonitor
194175
| CHECK-NEXT: connect monitor.clock, clock
@@ -347,14 +328,20 @@ class InlineTestSpec extends AnyFlatSpec with FileCheck {
347328
| CHECK: public module test_ModuleWithTests_foo
348329
| CHECK-NEXT: input clock : Clock
349330
| CHECK-NEXT: input reset : ${resetType}
331+
| CHECK-NEXT: output finish : UInt<1>
332+
| CHECK-NEXT: output success : UInt<1>
350333
|
351334
| CHECK: public module test_ModuleWithTests_bar
352335
| CHECK-NEXT: input clock : Clock
353336
| CHECK-NEXT: input reset : ${resetType}
337+
| CHECK-NEXT: output finish : UInt<1>
338+
| CHECK-NEXT: output success : UInt<1>
354339
|
355340
| CHECK: public module test_ModuleWithTests_with_result
356341
| CHECK-NEXT: input clock : Clock
357342
| CHECK-NEXT: input reset : ${resetType}
343+
| CHECK-NEXT: output finish : UInt<1>
344+
| CHECK-NEXT: output success : UInt<1>
358345
|
359346
| CHECK: public module test_ModuleWithTests_with_monitor
360347
| CHECK-NEXT: input clock : Clock
@@ -363,6 +350,8 @@ class InlineTestSpec extends AnyFlatSpec with FileCheck {
363350
| CHECK: public module test_ModuleWithTests_check2
364351
| CHECK-NEXT: input clock : Clock
365352
| CHECK-NEXT: input reset : ${resetType}
353+
| CHECK-NEXT: output finish : UInt<1>
354+
| CHECK-NEXT: output success : UInt<1>
366355
"""
367356

368357
emitCHIRRTL(new ModuleWithTests(resetType = Module.ResetType.Synchronous), args = argsElaborateAllTests)
@@ -385,6 +374,8 @@ class InlineTestSpec extends AnyFlatSpec with FileCheck {
385374
| CHECK: public module test_RawModuleWithTests_foo
386375
| CHECK-NEXT: input clock : Clock
387376
| CHECK-NEXT: input reset : UInt<1>
377+
| CHECK-NEXT: output finish : UInt<1>
378+
| CHECK-NEXT: output success : UInt<1>
388379
"""
389380
)
390381
}

0 commit comments

Comments
 (0)