Skip to content

Commit e77cdb0

Browse files
committed
VCD generation added
1 parent 95b9899 commit e77cdb0

2 files changed

Lines changed: 99 additions & 82 deletions

File tree

add-src/main/scala/chiseltest/ChiselScalatestTester.scala

Lines changed: 66 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package chiseltest
55
import chisel3._
66
import chisel3.simulator.EphemeralSimulator._
77
import chisel3.simulator.ChiselSim
8+
import svsim._
9+
import svsim.verilator.Backend.CompilationSettings.{TraceStyle, TraceKind}
810
import scala.language.implicitConversions
911

1012
/**
@@ -13,10 +15,13 @@ import scala.language.implicitConversions
1315
* This trait provides the ChiselTest API that users are familiar with from Chisel 6,
1416
* but internally uses ChiselSim from Chisel 7 to perform the actual testing.
1517
*
18+
* VCD GENERATION SUPPORT:
19+
* This compatibility layer supports VCD generation when WriteVcdAnnotation is used.
20+
* VCD files are generated in: build/chiselsim/<timestamp>/workdir-verilator/trace.vcd
21+
*
1622
* AUTOMATIC RESET FEATURE:
1723
* By default, this trait automatically resets the module before running tests,
18-
* mimicking ChiselTest's behavior from Chisel 6. This fixed 18 failing tests in
19-
* the book test suite (64% improvement).
24+
* mimicking ChiselTest's behavior from Chisel 6.
2025
*
2126
* To disable auto-reset or customize the reset duration:
2227
* {{{
@@ -26,21 +31,21 @@ import scala.language.implicitConversions
2631
* }
2732
* }}}
2833
*
29-
* Example usage:
34+
* Example usage with VCD:
3035
* {{{
3136
* import chiseltest._
3237
* import org.scalatest.flatspec.AnyFlatSpec
3338
*
3439
* class MyModuleSpec extends AnyFlatSpec with ChiselScalatestTester {
3540
* behavior of "MyModule"
3641
*
37-
* it should "work correctly" in {
38-
* test(new MyModule) { dut =>
39-
* // Module is already reset at this point
42+
* it should "generate waveforms" in {
43+
* test(new MyModule).withAnnotations(Seq(WriteVcdAnnotation)) { dut =>
4044
* dut.io.in.poke(42.U)
4145
* dut.clock.step()
4246
* dut.io.out.expect(42.U)
4347
* }
48+
* // VCD file: build/chiselsim/<timestamp>/workdir-verilator/trace.vcd
4449
* }
4550
* }
4651
* }}}
@@ -80,8 +85,6 @@ trait ChiselScalatestTester {
8085

8186
// Allow direct execution without annotations
8287
def apply(body: T => Unit): Unit = {
83-
// Use ChiselSim for consistent persistent output
84-
// Let ChiselSim use its default directory (build/chiselsim/*)
8588
val chiselSim = new ChiselSim {}
8689
chiselSim.simulate(dutGen) { dut =>
8790
if (autoReset) {
@@ -93,21 +96,55 @@ trait ChiselScalatestTester {
9396
}
9497

9598
/**
96-
* Runner class that executes the test
99+
* Runner class that executes the test with VCD support
97100
*/
98101
class TestRunner[T <: Module](dutGen: => T, autoReset: Boolean, resetCyc: Int, annotations: Seq[Any] = Seq()) {
99102
def apply(body: T => Unit): Unit = {
100-
// Keep annotations in signature for ChiselTest API compatibility.
101-
val _ = annotations
103+
// Check if WriteVcdAnnotation is present
104+
val hasVcd = annotations.exists {
105+
case _: chiseltest.WriteVcdAnnotation.type => true
106+
case _ => false
107+
}
102108

103-
// Use ChiselSim for simulation execution.
104109
val chiselSim = new ChiselSim {}
105110

106-
chiselSim.simulate(dutGen) { dut =>
107-
if (autoReset) {
108-
applyReset(dut, resetCyc)
111+
if (hasVcd) {
112+
println("[ChiselTest Compat] WriteVcdAnnotation detected, enabling VCD trace generation...")
113+
114+
// Create backend modification to enable VCD tracing
115+
implicit val backendMod: BackendSettingsModifications =
116+
(settings: Backend.Settings) => settings match {
117+
case vs: verilator.Backend.CompilationSettings =>
118+
val vcdStyle = TraceStyle(
119+
kind = TraceKind.Vcd,
120+
traceUnderscore = false,
121+
traceStructs = true,
122+
traceParams = true,
123+
maxWidth = None,
124+
maxArraySize = None,
125+
traceDepth = None
126+
)
127+
vs.withTraceStyle(Some(vcdStyle))
128+
case other => other
129+
}
130+
131+
// Simulate with VCD enabled
132+
chiselSim.simulate(dutGen) { dut =>
133+
chiselSim.enableWaves()
134+
135+
if (autoReset) {
136+
applyReset(dut, resetCyc)
137+
}
138+
body(dut)
139+
}
140+
} else {
141+
// Simulate without VCD
142+
chiselSim.simulate(dutGen) { dut =>
143+
if (autoReset) {
144+
applyReset(dut, resetCyc)
145+
}
146+
body(dut)
109147
}
110-
body(dut)
111148
}
112149
}
113150
}
@@ -116,7 +153,6 @@ trait ChiselScalatestTester {
116153
* Apply reset sequence to the DUT
117154
*/
118155
private def applyReset[T <: Module](dut: T, cycles: Int): Unit = {
119-
// Use ChiselSim testable helpers directly to avoid implicit ambiguity
120156
toTestableReset(dut.reset).poke(true.B)
121157
toTestableClock(dut.clock).step(cycles)
122158
toTestableReset(dut.reset).poke(false.B)
@@ -129,73 +165,41 @@ object ChiselTestCompat {
129165

130166
/**
131167
* Implicit class to add ChiselTest-style operations to Data types
132-
*
133-
* This provides the `poke`, `peek`, `expect` methods that ChiselTest users
134-
* are familiar with.
135168
*/
136169
implicit class testableData[T <: Data](val x: T) extends AnyVal {
137170

138171
/**
139172
* Poke a value onto a port
140-
*
141-
* @param value The value to poke (as a Chisel literal)
142173
*/
143-
def poke(value: T): Unit =
174+
def poke(value: T): Unit = {
144175
toTestableData(x).poke(value)
176+
}
145177

146178
/**
147179
* Peek the current value of a port
148-
*
149-
* @return The current value as a Chisel literal
150180
*/
151181
def peek(): T = {
152-
implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize
153182
toTestableData(x).peek()
154183
}
155184

156185
/**
157-
* Expect a specific value on a port
158-
*
159-
* @param value The expected value
160-
*/
161-
def expect(value: T): Unit = {
162-
implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize
163-
toTestableData(x).expect(value)
164-
}
165-
166-
/**
167-
* Expect a specific value on a port with a custom message
168-
*
169-
* @param value The expected value
170-
* @param message Custom error message if expectation fails
186+
* Assert that a port has an expected value
171187
*/
172-
def expect(value: T, message: String): Unit = {
173-
implicit val si: chisel3.experimental.SourceInfo = chisel3.experimental.SourceInfo.materialize
174-
toTestableData(x).expect(value, message)
188+
def expect(expected: T): Unit = {
189+
toTestableData(x).expect(expected)
175190
}
176191
}
177192

178-
/** Implicit class to add clock stepping operations */
179-
implicit class testableClock(val x: Clock) extends AnyVal {
180-
181-
/** Step the clock by one cycle */
182-
def step(): Unit =
183-
toTestableClock(x).step(1)
193+
/**
194+
* Implicit class to add ChiselTest-style operations to Clock types
195+
*/
196+
implicit class testableClock(val clock: Clock) extends AnyVal {
184197

185198
/**
186-
* Step the clock by a specified number of cycles
187-
*
188-
* @param cycles Number of cycles to step
199+
* Step the clock forward by a number of cycles
189200
*/
190-
def step(cycles: Int): Unit =
191-
toTestableClock(x).step(cycles)
192-
}
193-
194-
/** Implicit class to add reset operations */
195-
implicit class testableReset(val x: Reset) extends AnyVal {
196-
197-
/** Poke a value onto the reset signal */
198-
def poke(value: Bool): Unit =
199-
toTestableReset(x).poke(value.asInstanceOf[Reset])
201+
def step(cycles: Int = 1): Unit = {
202+
toTestableClock(clock).step(cycles)
203+
}
200204
}
201205
}

add-src/main/scala/chiseltest/README.md

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ This compatibility layer bridges that gap by providing the familiar ChiselTest A
1414

1515
**Automatic Reset** - Just like ChiselTest in Chisel 6, modules are automatically reset before each test
1616
**Drop-in Replacement** - Same imports, same API, works immediately
17-
**Configurable** - Override `autoResetEnabled` or `resetCycles` to customize behavior
17+
**Configurable** - Override `autoResetEnabled` or `resetCycles` to customize behavior
18+
**VCD Generation** - Waveform files can be generated with `WriteVcdAnnotation`
1819

1920
## Components
2021

@@ -38,6 +39,7 @@ ScalaTest integration trait that provides the test runner:
3839
- **`TestBuilder`** - Enables method chaining with `.withAnnotations()`
3940
- **`TestRunner`** - Executes the actual test via `simulate()`
4041
- **Configurable reset** - Override `autoResetEnabled` or `resetCycles` to customize behavior
42+
- **VCD generation** - Supports `WriteVcdAnnotation` for waveform capture
4143

4244
### 3. **formal/package.scala**
4345
Formal verification stubs (limited support):
@@ -109,12 +111,15 @@ class MyModuleTest extends AnyFlatSpec with ChiselScalatestTester {
109111
}
110112
```
111113

112-
### With Annotations (Ignored but Supported)
114+
### With VCD Waveform Generation
113115

114116
```scala
115117
test(new MyModule).withAnnotations(Seq(WriteVcdAnnotation)) { dut =>
116-
// Test code
118+
dut.io.input.poke(42.U)
119+
dut.clock.step()
120+
dut.io.output.expect(42.U)
117121
}
122+
// VCD file will be at: build/chiselsim/<timestamp>/workdir-verilator/trace.vcd
118123
```
119124

120125
### Decoupled Interface Testing
@@ -183,21 +188,19 @@ The auto-reset feature fixed **18 tests** that were previously failing due to un
183188

184189
## Limitations and Caveats
185190

186-
1. **VCD file generation** - **NOT SUPPORTED** in Chisel 7. ChiselSim does not provide APIs to enable VCD generation via `WriteVcdAnnotation`. Multiple approaches were tested:
187-
- System properties (`chisel.svsim.vcdOutput`)
188-
- Custom `HasTestingDirectory` implementations
189-
- Settings.copy with trace configuration
190-
- None of these approaches worked in Chisel 7.5.0
191+
1. **VCD file generation** - Use `WriteVcdAnnotation` to generate waveforms. The compatibility layer configures Verilator with trace support and calls `enableWaves()` at runtime. VCD files are generated at:
192+
```
193+
build/chiselsim/<timestamp>/workdir-verilator/trace.vcd
194+
```
195+
View with: `gtkwave build/chiselsim/*/workdir-verilator/trace.vcd`
191196

192-
2. **Annotation:** `WriteVcdAnnotation` is accepted but ignored. Multiple implementation approaches were tested (system properties, custom directories, Settings configuration) but none successfully enabled VCD output in ChiselSim 7.5.0.
197+
2. **Formal verification is limited** - `formal.verify()`, `past()`, and other formal constructs are stubs
193198

194-
3. **Formal verification is limited** - `formal.verify()`, `past()`, and other formal constructs are stubs
199+
3. **Fork/join has minimal support** - Concurrent testing patterns execute sequentially
195200

196-
4. **Fork/join has minimal support** - Concurrent testing patterns execute sequentially
201+
4. **Automatic reset behavior** - By default, modules are reset before each test (mimicking Chisel 6). Disable with `override def autoResetEnabled = false`
197202

198-
5. **Automatic reset behavior** - By default, modules are reset before each test (mimicking Chisel 6). Disable with `override def autoResetEnabled = false`
199-
200-
6. **Remaining test failures** - Some tests may fail due to:
203+
5. **Remaining test failures** - Some tests may fail due to:
201204
- Module elaboration errors (unknown port widths)
202205
- BlackBox Verilog width mismatches
203206
- Formal verification stub limitations
@@ -234,6 +237,7 @@ If you're migrating tests from Chisel 6:
234237
1. **Import statements** - Change `import chiseltest._` to use this compatibility layer (already in place in chisel-book)
235238
2. **Test structure** - Should be identical; no code changes needed
236239
3. **Run tests** - Use `sbt test` as before
240+
4. **VCD generation** - Works the same way with `WriteVcdAnnotation`, but files are in a different location
237241

238242
## Implementation Details
239243

@@ -242,9 +246,18 @@ If you're migrating tests from Chisel 6:
242246
1. User calls `test(dutGen)(body)` from the `ChiselScalatestTester` trait
243247
2. **Auto-reset** (if enabled): Asserts reset for `resetCycles` (default 1), then deasserts
244248
3. This returns a `TestBuilder` that accepts `.withAnnotations()` chaining
245-
4. The `TestRunner` eventually calls `chisel3.simulator.EphemeralSimulator.simulate(dutGen)(body)`
246-
5. Inside the test body, implicit conversions make Data types support `poke()`, `peek()`, `expect()`
247-
6. These methods delegate to ChiselSim's underlying `TestableData`, `TestableClock`, etc.
249+
4. The `TestRunner` checks for `WriteVcdAnnotation` and configures backend with VCD trace style
250+
5. Calls `chisel3.simulator.EphemeralSimulator.simulate(dutGen)(body)` with optional VCD support
251+
6. Inside the test body, implicit conversions make Data types support `poke()`, `peek()`, `expect()`
252+
7. These methods delegate to ChiselSim's underlying `TestableData`, `TestableClock`, etc.
253+
254+
### VCD Generation Technical Details
255+
256+
When `WriteVcdAnnotation` is detected, the compatibility layer:
257+
1. Creates an implicit `BackendSettingsModifications` with VCD `TraceStyle`
258+
2. Configures Verilator at compile time with trace support (`-DVM_TRACE_VCD=1`)
259+
3. Calls `chiselSim.enableWaves()` inside the simulation to start recording
260+
4. VCD file is written to `build/chiselsim/<timestamp>/workdir-verilator/trace.vcd`
248261

249262
### Mapping to ChiselSim
250263

@@ -263,9 +276,9 @@ When using the Chisel 7 + add-src layer:
263276
- `DeviceUnderTest.sv` - SystemVerilog (not FIRRTL)
264277
- `primary-sources/` - SystemVerilog modules
265278
- `workdir-verilator/` - Verilator compilation artifacts
266-
- `simulation` - Compiled executable
267-
- Simulation logs in `build/chiselsim/<timestamp>/workdir-verilator/`
268-
279+
- `trace.vcd` - Waveform file (when WriteVcdAnnotation is used)
280+
- `simulation` - Compiled executable
281+
- Simulation logs in `build/chiselsim/<timestamp>/workdir-verilator/`
269282

270283
## Future Work
271284

0 commit comments

Comments
 (0)