diff --git a/docs/src/appendix/migrating-from-chiseltest.md b/docs/src/appendix/migrating-from-chiseltest.md index 6c9d8e14dfb..5bb051df88f 100644 --- a/docs/src/appendix/migrating-from-chiseltest.md +++ b/docs/src/appendix/migrating-from-chiseltest.md @@ -125,3 +125,58 @@ println("`"*3) ChiselSim also does not currently have any support for `fork`-`join`, so any tests using those constructs will need to be rewritten in a single-threaded manner. + +## ChiselTest Compatibility Layer + +For projects with a large test suite, Chisel now includes a **ChiselTest compatibility layer** at `chiseltest._` that provides a drop-in replacement for most ChiselTest APIs. This allows you to keep your existing test code while running on ChiselSim. + +### Using the Compatibility Layer + +Simply keep your existing imports and test structure: + +```scala mdoc:reset:invisible +// Need to reset because EphemeralSimulator also provides peek and poke +import chisel3._ +class MyModule extends Module { + val io = IO(new Bundle { + val in = Input(UInt(16.W)) + val out = Output(UInt(16.W)) + }) + + io.out := RegNext(io.in) +} +``` + +```scala mdoc +import chisel3._ +import chiseltest._ +import org.scalatest.flatspec.AnyFlatSpec + +class MyModuleSpec extends AnyFlatSpec with ChiselScalatestTester { + behavior of "MyModule" + it should "do something" in { + test(new MyModule) { c => + c.io.in.poke(42.U) + c.clock.step() + c.io.out.expect(42.U) + } + } +} +``` + +The compatibility layer provides: +- `poke`, `peek`, `expect` methods on Data types +- `step()` methods on Clock +- `ChiselScalatestTester` trait for ScalaTest integration +- Automatic reset before each test (mimicking ChiselTest behavior) +- Decoupled interface utilities (`enqueueNow`, `expectDequeueNow`, etc.) +- Annotation stubs (`WriteVcdAnnotation`, etc.) for compatibility + +### Limitations + +- Annotations are ignored (waveforms always generated with Verilator) +- Formal verification support is limited to stubs +- `fork`/`join` patterns execute sequentially +- Some advanced features may require migration to ChiselSim APIs + +For full details, see `src/main/scala/chiseltest/README.md` in the Chisel repository. diff --git a/src/main/scala/chiseltest/ChiselScalatestTester.scala b/src/main/scala/chiseltest/ChiselScalatestTester.scala new file mode 100644 index 00000000000..6f6557b3ee5 --- /dev/null +++ b/src/main/scala/chiseltest/ChiselScalatestTester.scala @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiseltest + +import chisel3._ +import chisel3.simulator.EphemeralSimulator._ +import chisel3.simulator.ChiselSim +import svsim._ +import svsim.verilator.Backend.CompilationSettings.{TraceKind, TraceStyle} +import scala.language.implicitConversions + +/** + * ChiselTest-compatible API that delegates to ChiselSim (Chisel 7) + * + * This trait provides the ChiselTest API that users are familiar with from Chisel 6, + * but internally uses ChiselSim from Chisel 7 to perform the actual testing. + * + * VCD GENERATION SUPPORT: + * This compatibility layer supports VCD generation when WriteVcdAnnotation is used. + * VCD files are generated in: build/chiselsim//workdir-verilator/trace.vcd + * + * AUTOMATIC RESET FEATURE: + * By default, this trait automatically resets the module before running tests, + * mimicking ChiselTest's behavior from Chisel 6. + * + * To disable auto-reset or customize the reset duration: + * {{{ + * class MyTest extends AnyFlatSpec with ChiselScalatestTester { + * override def autoResetEnabled: Boolean = false // Disable auto-reset + * override def resetCycles: Int = 5 // Or change duration + * } + * }}} + * + * Example usage with VCD: + * {{{ + * import chiseltest._ + * import org.scalatest.flatspec.AnyFlatSpec + * + * class MyModuleSpec extends AnyFlatSpec with ChiselScalatestTester { + * behavior of "MyModule" + * + * it should "generate waveforms" in { + * test(new MyModule).withAnnotations(Seq(WriteVcdAnnotation)) { dut => + * dut.io.in.poke(42.U) + * dut.clock.step() + * dut.io.out.expect(42.U) + * } + * // VCD file: build/chiselsim//workdir-verilator/trace.vcd + * } + * } + * }}} + */ +trait ChiselScalatestTester { + + /** + * Enable automatic reset before each test. + * Override this to disable auto-reset if needed. + */ + def autoResetEnabled: Boolean = false + + /** + * Number of clock cycles to assert reset. + * Override this to change reset duration. + */ + def resetCycles: Int = 1 + + /** + * Test a module with the given stimulus + * + * This method provides the ChiselTest interface. + * + * @param dutGen A generator function that creates the device under test + * @tparam T The type of module being tested + */ + def test[T <: Module](dutGen: => T): TestBuilder[T] = + new TestBuilder(dutGen, autoResetEnabled, resetCycles) + + /** + * Builder class to support .withAnnotations() chaining + * + * @param dutGen Generator for the device under test + * @param autoReset Whether automatic reset is enabled + * @param resetCyc Number of reset cycles to apply + */ + class TestBuilder[T <: Module](dutGen: => T, autoReset: Boolean, resetCyc: Int) { + + /** + * Attach ChiselTest-style annotations before running the test. + * + * @param annotations Annotation list (for example WriteVcdAnnotation) + * @return A runner that executes the test with the provided annotations + */ + def withAnnotations(annotations: Seq[Any]): TestRunner[T] = { + new TestRunner(dutGen, autoReset, resetCyc, annotations) + } + + /** + * Run the test body without explicit annotations. + * + * @param body User-defined test body operating on the DUT instance + */ + // Allow direct execution without annotations + def apply(body: T => Unit): Unit = { + val chiselSim = new ChiselSim {} + chiselSim.simulate(dutGen) { dut => + if (autoReset) { + applyReset(dut, resetCyc) + } + body(dut) + } + } + } + + /** + * Runner class that executes the test with VCD support + * + * @param dutGen Generator for the device under test + * @param autoReset Whether automatic reset is enabled + * @param resetCyc Number of reset cycles to apply + * @param annotations Annotation list used to configure execution behavior + */ + class TestRunner[T <: Module](dutGen: => T, autoReset: Boolean, resetCyc: Int, annotations: Seq[Any] = Seq()) { + + /** + * Execute the configured test run. + * + * @param body User-defined test body operating on the DUT instance + */ + def apply(body: T => Unit): Unit = { + // Check if WriteVcdAnnotation is present + val hasVcd = annotations.exists { + case _: chiseltest.WriteVcdAnnotation.type => true + case _ => false + } + + val chiselSim = new ChiselSim {} + + if (hasVcd) { + println("[ChiselTest Compat] WriteVcdAnnotation detected, enabling VCD trace generation...") + + // Create backend modification to enable VCD tracing + implicit val backendMod: BackendSettingsModifications = + (settings: Backend.Settings) => + settings match { + case vs: verilator.Backend.CompilationSettings => + val vcdStyle = TraceStyle( + kind = TraceKind.Vcd, + traceUnderscore = false, + traceStructs = true, + traceParams = true, + maxWidth = None, + maxArraySize = None, + traceDepth = None + ) + vs.withTraceStyle(Some(vcdStyle)) + case other => other + } + + // Simulate with VCD enabled + chiselSim.simulate(dutGen) { dut => + chiselSim.enableWaves() + + if (autoReset) { + applyReset(dut, resetCyc) + } + body(dut) + } + } else { + // Simulate without VCD + chiselSim.simulate(dutGen) { dut => + if (autoReset) { + applyReset(dut, resetCyc) + } + body(dut) + } + } + } + } + + /** + * Apply reset sequence to the DUT + * + * @param dut Device under test + * @param cycles Number of cycles reset stays asserted + */ + private def applyReset[T <: Module](dut: T, cycles: Int): Unit = { + toTestableReset(dut.reset).poke(true.B) + toTestableClock(dut.clock).step(cycles) + toTestableReset(dut.reset).poke(false.B) + } +} diff --git a/src/main/scala/chiseltest/DecoupledDriver.scala b/src/main/scala/chiseltest/DecoupledDriver.scala new file mode 100644 index 00000000000..89ac2ed8cab --- /dev/null +++ b/src/main/scala/chiseltest/DecoupledDriver.scala @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiseltest + +/** + * Compatibility placeholder for ChiselTest's DecoupledDriver class. + * + * Decoupled enqueue/dequeue helpers are provided via implicit extensions in + * `package object chiseltest`. Use `import chiseltest._` to access: + * - `enqueueNow`, `enqueueSeq` + * - `expectDequeueNow`, `expectDequeueSeq` + * - `initSource`, `initSink` + */ +final class DecoupledDriver diff --git a/src/main/scala/chiseltest/README.md b/src/main/scala/chiseltest/README.md new file mode 100644 index 00000000000..1d0b6d14a3c --- /dev/null +++ b/src/main/scala/chiseltest/README.md @@ -0,0 +1,187 @@ +# ChiselTest Compatibility Layer for Chisel 7 + +This directory contains a compatibility layer that enables existing code to use the **ChiselTest API** while running on **Chisel 7.5.0** with **ChiselSim**. + +## Overview + +The Chisel language underwent a major transition from Chisel 6 to Chisel 7: +- **Chisel 6** used the `ChiselTest` library for hardware simulation +- **Chisel 7** replaced it with `ChiselSim`, requiring all tests to be rewritten + +This compatibility layer bridges that gap by providing the familiar ChiselTest API as a thin wrapper around ChiselSim, allowing existing code to run without modification. + +## Key Features + +✅ **Optional Auto Reset** - Disabled by default; enable per test suite and configure reset cycles +✅ **Drop-in Replacement** - Same imports, same API, works immediately +✅ **VCD Support** - `WriteVcdAnnotation` enables VCD waveform generation +✅ **Configurable** - Override `autoResetEnabled` or `resetCycles` to customize behavior + +## Components + +### 1. **package.scala** +Core compatibility layer providing implicit conversions and extension methods: + +- **`testableData[T]`** - Adds poke/peek/expect methods to any Data type +- **`testableUInt`** - Specialized handling for UInt with `peekInt()` support +- **`testableBoolExt`** - Specialized handling for Bool with `peekBoolean()` support +- **`testableClock`** - Adds `step()` and `setTimeout()` methods to Clock +- **`testableReset`** - Adds poke support to Reset signals +- **`DecoupledIOOps[T]`** - Utilities for testing Decoupled interfaces (enqueueNow, expectDequeueNow, etc.) +- **Annotation stubs** - `WriteVcdAnnotation`, `VerilatorBackendAnnotation` for compatibility +- **`fork` function** - Stub implementation for concurrent test patterns + +### 2. **ChiselScalatestTester.scala** +ScalaTest integration trait that provides the test runner: + +- **`ChiselScalatestTester`** trait - Mix into your test class to enable `test()` method +- **Optional auto reset** - Applies reset only when enabled +- **`TestBuilder`** - Enables method chaining with `.withAnnotations()` +- **`TestRunner`** - Executes the actual test via `simulate()` +- **Configurable reset** - Override `autoResetEnabled` or `resetCycles` to customize behavior +- **VCD waveform generation** - Enabled with `WriteVcdAnnotation` + +### 3. **formal/package.scala** +Formal verification stubs (limited support): + +- **`Formal`** trait - For formal verification test patterns +- **`BoundedCheck`** - Annotation for bounded model checking +- **`past()` function** - Temporal operator stub + +## Usage + +### Basic Test Pattern + +```scala +import chisel3._ +import chiseltest._ +import org.scalatest.flatspec.AnyFlatSpec + +class MyModuleTest extends AnyFlatSpec with ChiselScalatestTester { + "MyModule" should "work" in { + test(new MyModule) { dut => + // Auto-reset is disabled by default, so reset manually if needed + dut.reset.poke(true.B) + dut.clock.step(1) + dut.reset.poke(false.B) + + dut.io.input.poke(42.U) + dut.clock.step() + dut.io.output.expect(42.U) + } + } +} +``` + +### Enable Automatic Reset + +If you want ChiselTest-style reset behavior: + +```scala +class MyModuleTest extends AnyFlatSpec with ChiselScalatestTester { + // Enable automatic reset + override def autoResetEnabled: Boolean = true + override def resetCycles: Int = 5 + + "MyModule" should "work" in { + test(new MyModule) { dut => + // Module has been reset automatically for 5 cycles + dut.io.input.poke(42.U) + dut.clock.step() + dut.io.output.expect(42.U) + } + } +} +``` + +### Custom Reset Duration + +```scala +class MyModuleTest extends AnyFlatSpec with ChiselScalatestTester { + // Reset for 5 cycles instead of default 1 + override def resetCycles: Int = 5 + + "MyModule" should "work" in { + test(new MyModule) { dut => + // Module has been reset for 5 cycles + dut.io.input.poke(42.U) + dut.clock.step() + dut.io.output.expect(42.U) + } + } +} +``` + +### With Annotations (VCD Supported) + +```scala +test(new MyModule).withAnnotations(Seq(WriteVcdAnnotation)) { dut => + // Test code; VCD trace is generated +} +``` + +`WriteVcdAnnotation` generates `trace.vcd` in the simulation work directory under `build/chiselsim/...`. + +### Decoupled Interface Testing + +```scala +implicit val clk: Clock = dut.clock + +dut.io.in.initSource() +dut.io.out.initSink() + +dut.io.in.enqueueNow(10.U) +dut.io.out.expectDequeueNow(10.U) +``` + +## API Reference + +### Poke, Peek, Expect + +```scala +dut.io.signal.poke(42.U) // Drive a signal +val value = dut.io.signal.peek() // Read current value +dut.io.signal.expect(42.U) // Assert expected value +``` + +### Type-Specific Methods + +```scala +// UInt +dut.io.counter.peekInt() // Peek as BigInt + +// Bool +dut.io.flag.peekBoolean() // Peek as Scala Boolean +``` + +### Clock Control + +```scala +dut.clock.step() // Step one cycle +dut.clock.step(10) // Step 10 cycles +dut.clock.setTimeout(10000) // Set timeout (ignored in ChiselSim) +``` + +### Decoupled Helpers + +```scala +dut.io.in.enqueueNow(data) // Send one value +dut.io.in.enqueueSeq(Seq(v1, v2, v3)) // Send sequence +dut.io.out.expectDequeueNow(data) // Expect one value +dut.io.out.expectDequeueSeq(Seq(v1, v2)) // Expect sequence +dut.io.in.initSource() // Initialize source side +dut.io.out.initSink() // Initialize sink side +``` + +## Testing Examples + +See the test files in `src/test/scala/` for comprehensive examples. + +## Migration from Chisel 6 + +If you're migrating code from Chisel 6: + +1. **Import statements** - Change `import chiseltest._` to use this compatibility layer +2. **Test structure** - Should be identical; no code changes needed +3. **Run tests** - Use `mill` or your build tool as before + diff --git a/src/main/scala/chiseltest/formal/package.scala b/src/main/scala/chiseltest/formal/package.scala new file mode 100644 index 00000000000..6cf0e2827a0 --- /dev/null +++ b/src/main/scala/chiseltest/formal/package.scala @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiseltest + +import chisel3._ +import scala.annotation.compileTimeOnly + +/** + * Formal compatibility API placeholders. + * + * Formal verification is currently unsupported in this compatibility layer. + * Any usage should fail at compile time to avoid vacuously passing tests. + */ +package object formal { + + /** Annotation placeholder for source compatibility only. */ + case class BoundedCheck(depth: Int) + + @compileTimeOnly("chiseltest.formal.Formal is unsupported in this compatibility layer") + trait Formal { + @compileTimeOnly("chiseltest.formal.verify is unsupported in this compatibility layer") + def verify[T <: Module](dut: => T, annotations: Seq[Any]): Unit = + throw new UnsupportedOperationException("chiseltest.formal.verify is unsupported") + } + + @compileTimeOnly("chiseltest.formal.past is unsupported in this compatibility layer") + def past[T <: Data](x: T, delay: Int = 1): T = + throw new UnsupportedOperationException("chiseltest.formal.past is unsupported") + + @compileTimeOnly("chiseltest.formal.past is unsupported in this compatibility layer") + def past[T <: Data](x: T): T = + throw new UnsupportedOperationException("chiseltest.formal.past is unsupported") +} diff --git a/src/main/scala/chiseltest/package.scala b/src/main/scala/chiseltest/package.scala new file mode 100644 index 00000000000..2076f39f6cd --- /dev/null +++ b/src/main/scala/chiseltest/package.scala @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * ChiselTest Compatibility Layer for Chisel 7 + * + * This package provides a drop-in replacement for the ChiselTest library that was removed in Chisel 7. + * It preserves the familiar ChiselTest API while delegating to Chisel 7's ChiselSim underneath. + * + * Usage: + * {{{ + * import chiseltest._ + * import org.scalatest.flatspec.AnyFlatSpec + * + * class MyTest extends AnyFlatSpec with ChiselScalatestTester { + * it should "work" in { + * test(new MyModule) { dut => + * dut.io.in.poke(42.U) + * dut.clock.step() + * dut.io.out.expect(42.U) + * } + * } + * } + * }}} + * + * Key Components: + * - testableData, testableUInt, testableBoolExt: Implicit conversions for poke/peek/expect + * - testableClock: Clock stepping and control + * - DecoupledIOOps: Utilities for Decoupled interface testing + * - ChiselScalatestTester: ScalaTest integration trait + * + * See README.md for detailed documentation. + */ +package object chiseltest { + import scala.language.implicitConversions + import chisel3._ + import chisel3.simulator.EphemeralSimulator._ + import chisel3.util.DecoupledIO + + // Dummy annotations for compatibility (ignored in Chisel 7) + case object WriteVcdAnnotation + case object VerilatorBackendAnnotation + + // Implicit conversions for ChiselTest-compatible operations + implicit class testableData[T <: Data](val x: T) extends AnyVal { + def poke(value: T): Unit = + toTestableData(x).poke(value) + + def peek(): T = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableData(x).peek() + } + + def expect(value: T): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableData(x).expect(value) + } + + def expect(value: T, message: String): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableData(x).expect(value, message) + } + + // Add pokePartial for Bundle Lit values + def pokePartial(litBundle: T): Unit = { + // For partial bundle literals, only poke fields that have been set + (x, litBundle) match { + case (dut: Record, lit: Record) => + dut.elements.foreach { case (name, dutField) => + lit.elements.get(name).foreach { litField => + // Only poke if the literal field has a value set + if (litField.litOption.isDefined) { + toTestableData(dutField).poke(litField) + } + } + } + case _ => + // For non-bundle types, just poke directly + toTestableData(x).poke(litBundle) + } + } + + // Add expectPartial for Bundle Lit values + def expectPartial(litBundle: T): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + // For partial bundle literals, only check fields that have been set + (x, litBundle) match { + case (dut: Record, lit: Record) => + dut.elements.foreach { case (name, dutField) => + lit.elements.get(name).foreach { litField => + // Only expect if the literal field has a value set + if (litField.litOption.isDefined) { + toTestableData(dutField).expect(litField) + } + } + } + case _ => + // For non-bundle types, just expect directly + toTestableData(x).expect(litBundle) + } + } + } + + // Enhanced UInt operations + implicit class testableUInt(val x: UInt) extends AnyVal { + def poke(value: UInt): Unit = + toTestableData(x).poke(value) + + def poke(value: BigInt): Unit = + toTestableData(x).poke(value.U) + + def poke(value: Int): Unit = + toTestableData(x).poke(value.U) + + def peek(): UInt = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableData(x).peek().asInstanceOf[UInt] + } + + def peekInt(): BigInt = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableData(x).peek().asInstanceOf[UInt].litValue + } + + def expect(value: UInt): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableData(x).expect(value) + } + + def expect(value: BigInt): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableData(x).expect(value.U) + } + + def expect(value: Int): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableData(x).expect(value.U) + } + } + + // Enhanced Bool operations + implicit class testableBoolExt(val x: Bool) extends AnyVal { + def poke(value: Bool): Unit = + toTestableBool(x).poke(value) + + def poke(value: Boolean): Unit = + toTestableBool(x).poke(value.B) + + def peek(): Bool = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableBool(x).peek() + } + + def peekBoolean(): Boolean = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableBool(x).peek().litToBoolean + } + + def expect(value: Bool): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableBool(x).expect(value) + } + + def expect(value: Boolean): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableBool(x).expect(value.B) + } + } + + implicit class testableClock(val x: Clock) extends AnyVal { + def step(): Unit = + toTestableClock(x).step(1) + + def step(cycles: Int): Unit = + toTestableClock(x).step(cycles) + + // Stub for setTimeout - ignored in Chisel 7 + def setTimeout(cycles: Int): Unit = { + // ChiselSim doesn't have explicit timeouts, this is a no-op for compatibility + } + } + + implicit class testableReset(val x: Reset) extends AnyVal { + def poke(value: Bool): Unit = + toTestableReset(x).poke(value.asInstanceOf[Reset]) + } + + // Decoupled interface helper extensions + implicit class DecoupledIOOps[T <: Data](val x: DecoupledIO[T]) extends AnyVal { + def enqueueNow(data: T)(implicit clock: Clock): Unit = { + // Drive data and valid for one cycle + // This allows enqueueing even if consumer is not ready (data goes into queue) + toTestableData(x.bits).poke(data) + toTestableBool(x.valid).poke(true.B) + toTestableClock(clock).step(1) + toTestableBool(x.valid).poke(false.B) + } + + def enqueueSeq(data: Seq[T])(implicit clock: Clock): Unit = + data.foreach { d => + enqueueNow(d) + } + + def expectDequeueNow(data: T, maxCycles: Int = 1000)(implicit clock: Clock): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableBool(x.ready).poke(true.B) + // Wait until valid is seen before checking data + var waited = 0 + while (!toTestableBool(x.valid).peek().litToBoolean) { + if (waited >= maxCycles) { + throw new RuntimeException(s"expectDequeueNow: valid not asserted within $maxCycles cycles") + } + toTestableClock(clock).step(1) + waited += 1 + } + toTestableData(x.bits).expect(data) + toTestableClock(clock).step(1) + toTestableBool(x.ready).poke(false.B) + } + + def expectDequeueSeq(data: Seq[T])(implicit clock: Clock): Unit = + data.foreach { d => + expectDequeueNow(d) + } + + def expectPeek(data: T): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableData(x.bits).expect(data) + } + + def expectInvalid(): Unit = { + implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize + toTestableBool(x.valid).expect(false.B) + } + + def initSource()(implicit clock: Clock): Unit = + toTestableBool(x.valid).poke(false.B) + + def initSink()(implicit clock: Clock): Unit = + toTestableBool(x.ready).poke(false.B) + } + + // Stub for fork - not fully supported but allows compilation + def fork(body: => Unit): ForkHandle = { + // Execute immediately in Chisel 7 (no true concurrency support yet) + body + new ForkHandle + } + + class ForkHandle { + def join(): Unit = () + } +} diff --git a/src/test/scala/chiselTests/chiseltest/ExamplesTest.scala b/src/test/scala/chiselTests/chiseltest/ExamplesTest.scala new file mode 100644 index 00000000000..7d9af954412 --- /dev/null +++ b/src/test/scala/chiselTests/chiseltest/ExamplesTest.scala @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.chiseltest + +import chisel3._ +import chiseltest._ +import chiselTests.chiseltest.examples._ +import org.scalatest.flatspec.AnyFlatSpec + +/** + * Basic tests for the chiseltest examples + */ +class ExamplesBasicTest extends AnyFlatSpec with ChiselScalatestTester { + behavior of "PassthroughModule" + + it should "pass through input to output" in { + test(new PassthroughModule) { dut => + dut.io.in.poke(42.U) + dut.io.out.expect(42.U) + } + } + + it should "handle 0 input" in { + test(new PassthroughModule) { dut => + dut.io.in.poke(0.U) + dut.io.out.expect(0.U) + } + } + + it should "handle max 8-bit value" in { + test(new PassthroughModule) { dut => + dut.io.in.poke(255.U) + dut.io.out.expect(255.U) + } + } +} + +/** + * Tests for the SimpleCounter module + */ +class ExamplesCounterTest extends AnyFlatSpec with ChiselScalatestTester { + behavior of "SimpleCounter" + + it should "count when enabled" in { + test(new SimpleCounter) { dut => + dut.reset.poke(true.B) + dut.clock.step(1) + dut.reset.poke(false.B) + dut.clock.step(1) + + dut.io.en.poke(true.B) + dut.io.out.expect(0.U) + + for (i <- 1 to 5) { + dut.clock.step(1) + dut.io.out.expect(i.U) + } + } + } + + it should "hold value when disabled" in { + test(new SimpleCounter) { dut => + dut.reset.poke(true.B) + dut.clock.step(1) + dut.reset.poke(false.B) + dut.clock.step(1) + + dut.io.en.poke(true.B) + dut.clock.step(3) + dut.io.en.poke(false.B) + dut.clock.step(1) + dut.io.out.expect(3.U) + + dut.clock.step(2) + dut.io.out.expect(3.U) + } + } +} + +/** + * Tests for the SimpleRegister module + */ +class ExamplesRegisterTest extends AnyFlatSpec with ChiselScalatestTester { + behavior of "SimpleRegister" + + it should "store and retrieve data" in { + test(new SimpleRegister) { dut => + dut.reset.poke(true.B) + dut.clock.step(1) + dut.reset.poke(false.B) + dut.clock.step(1) + + dut.io.data_in.poke(42.U) + dut.io.write_en.poke(true.B) + dut.clock.step(1) + dut.io.data_out.expect(42.U) + } + } + + it should "not write when disabled" in { + test(new SimpleRegister) { dut => + dut.reset.poke(true.B) + dut.clock.step(1) + dut.reset.poke(false.B) + dut.clock.step(1) + + dut.io.data_in.poke(42.U) + dut.io.write_en.poke(true.B) + dut.clock.step(1) + dut.io.data_in.poke(100.U) + dut.io.write_en.poke(false.B) + dut.clock.step(1) + dut.io.data_out.expect(42.U) + } + } +} + +/** + * Tests for the SimpleQueue module + */ +class ExamplesQueueTest extends AnyFlatSpec with ChiselScalatestTester { + behavior of "SimpleQueue" + + it should "enqueue and dequeue a value" in { + test(new SimpleQueue) { dut => + dut.reset.poke(true.B) + dut.clock.step(1) + dut.reset.poke(false.B) + dut.clock.step(1) + + // Initially not valid + dut.io.deq_valid.expect(false.B) + dut.io.enq_ready.expect(true.B) + + // Enqueue a value + dut.io.enq_data.poke(42.U) + dut.io.enq_valid.poke(true.B) + dut.clock.step(1) + dut.io.enq_valid.poke(false.B) + + // Check dequeue valid and data + dut.io.deq_valid.expect(true.B) + dut.io.deq_data.expect(42.U) + + // Dequeue + dut.io.deq_ready.poke(true.B) + dut.clock.step(1) + + // Should be empty again + dut.io.deq_valid.expect(false.B) + } + } + + it should "hold data until dequeued" in { + test(new SimpleQueue) { dut => + dut.reset.poke(true.B) + dut.clock.step(1) + dut.reset.poke(false.B) + dut.clock.step(1) + + // Enqueue + dut.io.enq_data.poke(99.U) + dut.io.enq_valid.poke(true.B) + dut.clock.step(1) + + // Don't dequeue, just step + dut.io.enq_valid.poke(false.B) + dut.io.deq_ready.poke(false.B) + dut.clock.step(2) + + // Data should still be there + dut.io.deq_valid.expect(true.B) + dut.io.deq_data.expect(99.U) + } + } +} diff --git a/src/test/scala/chiselTests/chiseltest/examples/TestHelpers.scala b/src/test/scala/chiselTests/chiseltest/examples/TestHelpers.scala new file mode 100644 index 00000000000..68f942a00a6 --- /dev/null +++ b/src/test/scala/chiselTests/chiseltest/examples/TestHelpers.scala @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.chiseltest.examples + +import chisel3._ +import chiseltest._ +import org.scalatest.flatspec.AnyFlatSpec + +/** + * Utility trait for common test setup patterns + */ +trait ChiselTestHelper extends AnyFlatSpec with ChiselScalatestTester { + + /** + * Test a module with a basic poke and expect pattern + */ + protected def testPokeExpect[T <: Module]( + module: => T, + pokeData: (T, UInt) => Unit, + expectedData: UInt + ): Unit = { + test(module) { dut => + implicit val clk = dut.clock + dut.reset.poke(true.B) + dut.clock.step(2) + dut.reset.poke(false.B) + dut.clock.step(1) + + pokeData(dut, 42.U) + dut.clock.step(1) + } + } + + /** + * Test a sequential logic module with multiple cycles + */ + protected def testSequential[T <: Module]( + module: => T, + stimuli: Seq[(T) => Unit] + ): Unit = { + test(module) { dut => + implicit val clk = dut.clock + dut.reset.poke(true.B) + dut.clock.step(2) + dut.reset.poke(false.B) + dut.clock.step(1) + + stimuli.foreach { stimulus => + stimulus(dut) + dut.clock.step(1) + } + } + } +} diff --git a/src/test/scala/chiselTests/chiseltest/examples/package.scala b/src/test/scala/chiselTests/chiseltest/examples/package.scala new file mode 100644 index 00000000000..3e4cd14eac9 --- /dev/null +++ b/src/test/scala/chiselTests/chiseltest/examples/package.scala @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.chiseltest + +/** + * ChiselTest Examples and Test Utilities + * + * This package provides example modules and utilities for testing with the + * ChiselTest compatibility layer on ChiselSim. + */ +package object examples { + import chisel3._ + + /** + * A simple passthrough module for basic testing + */ + class PassthroughModule extends Module { + val io = IO(new Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + }) + io.out := io.in + } + + /** + * A simple counter module + */ + class SimpleCounter extends Module { + val io = IO(new Bundle { + val en = Input(Bool()) + val out = Output(UInt(8.W)) + }) + + val count = RegInit(0.U(8.W)) + + when(io.en) { + count := count + 1.U + } + + io.out := count + } + + /** + * A simple register with write enable + */ + class SimpleRegister extends Module { + val io = IO(new Bundle { + val data_in = Input(UInt(8.W)) + val write_en = Input(Bool()) + val data_out = Output(UInt(8.W)) + }) + + val reg = RegInit(0.U(8.W)) + + when(io.write_en) { + reg := io.data_in + } + + io.data_out := reg + } + + /** + * A simple FIFO-like queue + */ + class SimpleQueue extends Module { + val io = IO(new Bundle { + val enq_data = Input(UInt(8.W)) + val enq_valid = Input(Bool()) + val enq_ready = Output(Bool()) + val deq_data = Output(UInt(8.W)) + val deq_valid = Output(Bool()) + val deq_ready = Input(Bool()) + }) + + val storage = RegInit(0.U(8.W)) + val valid = RegInit(false.B) + + // Enqueue side + io.enq_ready := !valid || (valid && io.deq_ready) + + when(reset.asBool) { + valid := false.B + }.elsewhen(io.enq_valid && io.enq_ready) { + storage := io.enq_data + valid := true.B + }.elsewhen(io.deq_valid && io.deq_ready) { + valid := false.B + } + + // Dequeue side + io.deq_data := storage + io.deq_valid := valid + } + +}