Skip to content

Commit dd04d99

Browse files
authored
[chiselsim] Add randomization to settings (#4835)
Add the ability to control the existing FIRRTL randomization settings via an additional parameter to `chisel3.simulator.Settings`. Set the default behavior for this to always randomize. This is a reasonable default for Verilator or any other 2-state simulator. For 4-state simulators, users may want to deviate and use the provided `uniniitialized` randomization. This commit intentionally does not try to struggle with setting per-simulator settings that are different from each other. While I think this could make sense (you would like to use randomization for Verilator and uninitialized w/ x-prop for VCS), I think that the logic necessary to handling this is going to be too opaque for most users. I would rather keep this as a single setting on simulate and any simulator you use will then simulate the same thing. If you want to do something different, then it is on the user. Note: I don't expect most users to care about running different simulators, in spite of the fact that we provide the `Cli.Simulator` trait (and intentionally do not mix it into `ChiselSim`). If users _do_ care about running different simulators, then they would be better served by enumerating these as explicit tests. I.e., I am not super concerned about the use case of writing a test with one randomization mode and then expecting the randomization mode to morph if the user changes the simulator on the command line. Signed-off-by: Schuyler Eldridge <[email protected]>
1 parent 397804d commit dd04d99

File tree

4 files changed

+200
-13
lines changed

4 files changed

+200
-13
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package chisel3.simulator
4+
5+
import svsim.CommonCompilationSettings.VerilogPreprocessorDefine
6+
7+
/** A description of how a Chisel circuit should be randomized
8+
*
9+
* @param registers if true, randomize the initial state of registers
10+
* @param memories if true, randomize the initial state of memories
11+
* @param garbageAssign
12+
* @param invalidAssign
13+
* @param delay an optional delay value to apply to the randomization. This
14+
* will cause the randomization to be applied this many Verilog time units
15+
* after the simulation starts.
16+
* @throws IllegalArgumentException if register and memory randomization are
17+
* both disabled and delay or randomValue are non-empty
18+
*/
19+
final class Randomization(
20+
val registers: Boolean,
21+
val memories: Boolean,
22+
val delay: Option[Int],
23+
val randomValue: Option[String]
24+
) {
25+
26+
require(
27+
registers || memories || (delay.isEmpty && randomValue.isEmpty),
28+
"when register and memory randomization is disabled, then `delay` and `randomValue` should be empty as they have no effect"
29+
)
30+
31+
require(
32+
delay.isEmpty || delay.get != 0,
33+
"a delay of zero is illegal as this can conflict with initial blocks and simulator-specific time-zero behavior"
34+
)
35+
36+
/** Create a copy of this [[Randomization]] changing some parameters */
37+
def copy(
38+
registers: Boolean = registers,
39+
memories: Boolean = memories,
40+
delay: Option[Int] = delay,
41+
randomValue: Option[String] = randomValue
42+
) = new Randomization(registers, memories, delay, randomValue)
43+
44+
/** Convert this to Verilog preprocessor defines */
45+
private[simulator] def toPreprocessorDefines: Seq[VerilogPreprocessorDefine] = {
46+
Option.when(registers)(VerilogPreprocessorDefine("RANDOMIZE_REG_INIT")).toSeq ++
47+
Option.when(memories)(VerilogPreprocessorDefine("RANDOMIZE_MEM_INIT")) ++
48+
delay.map(d => VerilogPreprocessorDefine("RANDOMIZE_DELAY", d.toString)) ++
49+
randomValue.map(VerilogPreprocessorDefine("RANDOM", _))
50+
}
51+
52+
}
53+
54+
object Randomization {
55+
56+
/** Randomize nothing
57+
*
58+
* This will cause the simulation to be brought up in whatever state the
59+
* simulator brings up a simulation in. If the simulator supports `x`, then
60+
* uninitialized hardware will be brought up in `x`. However, if the
61+
* simulator is two-state (e.g., Verilator), then it will be brought up in a
62+
* simulator-dependent state.
63+
*/
64+
def uninitialized = new Randomization(
65+
registers = false,
66+
memories = false,
67+
delay = None,
68+
randomValue = None
69+
)
70+
71+
/** Randomize everything
72+
*
73+
* This will randomize everything that the FIRRTL/Verilog ABI allows. All
74+
* Verilog that Chisel produces will have a random two-state value. Verilog
75+
* that Chisel does not have control of (e.g., blackboxes) will be brought up
76+
* in a different state unless they opt-in to the FIRRTL/Verilog ABI.
77+
*
78+
* Non-two-state values (i.e., `x`)
79+
*
80+
* @note The FIRRTL/Verilog ABI for randomization is undocumented in the
81+
* FIRRTL ABI specification.
82+
*/
83+
def random = new Randomization(
84+
registers = true,
85+
memories = true,
86+
delay = Some(1),
87+
randomValue = Some("$urandom")
88+
)
89+
90+
}

src/main/scala/chisel3/simulator/Settings.scala

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ object MacroText {
8080
* simulation runtime.
8181
* @param enableWavesAtTimeZero enable waveform dumping at time zero. This
8282
* requires a simulator capable of dumping waves.
83+
* @param randomization random initialization settings to use
8384
*/
8485
final class Settings[A <: RawModule] private[simulator] (
8586
/** Layers to turn on/off during Verilog elaboration */
@@ -88,7 +89,8 @@ final class Settings[A <: RawModule] private[simulator] (
8889
val printfCond: Option[MacroText.Type[A]],
8990
val stopCond: Option[MacroText.Type[A]],
9091
val plusArgs: Seq[svsim.PlusArg],
91-
val enableWavesAtTimeZero: Boolean
92+
val enableWavesAtTimeZero: Boolean,
93+
val randomization: Randomization
9294
) {
9395

9496
def copy(
@@ -97,9 +99,10 @@ final class Settings[A <: RawModule] private[simulator] (
9799
printfCond: Option[MacroText.Type[A]] = printfCond,
98100
stopCond: Option[MacroText.Type[A]] = stopCond,
99101
plusArgs: Seq[svsim.PlusArg] = plusArgs,
100-
enableWavesAtTimeZero: Boolean = enableWavesAtTimeZero
102+
enableWavesAtTimeZero: Boolean = enableWavesAtTimeZero,
103+
randomization: Randomization = randomization
101104
) =
102-
new Settings(verilogLayers, assertVerboseCond, printfCond, stopCond, plusArgs, enableWavesAtTimeZero)
105+
new Settings(verilogLayers, assertVerboseCond, printfCond, stopCond, plusArgs, enableWavesAtTimeZero, randomization)
103106

104107
private[simulator] def preprocessorDefines(
105108
elaboratedModule: ElaboratedModule[A]
@@ -112,7 +115,7 @@ final class Settings[A <: RawModule] private[simulator] (
112115
).flatMap {
113116
case (Some(a), macroName) => Some(a.toPreprocessorDefine(macroName, elaboratedModule))
114117
case (None, _) => None
115-
} ++ verilogLayers.preprocessorDefines(elaboratedModule)
118+
} ++ verilogLayers.preprocessorDefines(elaboratedModule) ++ randomization.toPreprocessorDefines
116119

117120
}
118121

@@ -146,7 +149,8 @@ object Settings {
146149
printfCond = Some(MacroText.NotSignal(get = _.reset)),
147150
stopCond = Some(MacroText.NotSignal(get = _.reset)),
148151
plusArgs = Seq.empty,
149-
enableWavesAtTimeZero = false
152+
enableWavesAtTimeZero = false,
153+
randomization = Randomization.random
150154
)
151155

152156
/** Retun a default [[Settings]] for a [[RawModule]].
@@ -174,7 +178,8 @@ object Settings {
174178
printfCond = None,
175179
stopCond = None,
176180
plusArgs = Seq.empty,
177-
enableWavesAtTimeZero = false
181+
enableWavesAtTimeZero = false,
182+
randomization = Randomization.random
178183
)
179184

180185
/** Simple factory for construcing a [[Settings]] from arguments.
@@ -196,14 +201,16 @@ object Settings {
196201
printfCond: Option[MacroText.Type[A]],
197202
stopCond: Option[MacroText.Type[A]],
198203
plusArgs: Seq[svsim.PlusArg],
199-
enableWavesAtTimeZero: Boolean
204+
enableWavesAtTimeZero: Boolean,
205+
randomization: Randomization
200206
): Settings[A] = new Settings(
201207
verilogLayers = verilogLayers,
202208
assertVerboseCond = assertVerboseCond,
203209
printfCond = printfCond,
204210
stopCond = stopCond,
205211
plusArgs = plusArgs,
206-
enableWavesAtTimeZero = enableWavesAtTimeZero
212+
enableWavesAtTimeZero = enableWavesAtTimeZero,
213+
randomization = randomization
207214
)
208215

209216
}

src/test/scala-2/chiselTests/RecordSpec.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import scala.collection.immutable.{ListMap, SeqMap, VectorMap}
1616

1717
object RecordSpec {
1818
class MyBundle extends Bundle {
19-
val foo = UInt(32.W)
20-
val bar = UInt(32.W)
19+
val foo = UInt(16.W)
20+
val bar = UInt(16.W)
2121
}
2222
// Useful for constructing types from CustomBundle
2323
// This is a def because each call to this needs to return a new instance
24-
def fooBarType: CustomBundle = new CustomBundle("foo" -> UInt(32.W), "bar" -> UInt(32.W))
24+
def fooBarType: CustomBundle = new CustomBundle("foo" -> UInt(16.W), "bar" -> UInt(16.W))
2525

2626
class MyModule(output: => Record, input: => Record) extends Module {
2727
val io = IO(new Bundle {

src/test/scala-2/chiselTests/simulator/scalatest/ChiselSimSpec.scala

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ package chiselTests.simulator.scalatest
44

55
import chisel3._
66
import chisel3.simulator.PeekPokeAPI.FailedExpectationException
7-
import chisel3.simulator.{ChiselSim, HasSimulator, MacroText, Settings}
7+
import chisel3.simulator.{HasSimulator, MacroText, Randomization, Settings}
8+
import chisel3.simulator.scalatest.ChiselSim
89
import chisel3.simulator.stimulus.RunUntilSuccess
910
import chisel3.testing.HasTestingDirectory
1011
import chisel3.testing.scalatest.{FileCheck, TestingDirectory}
@@ -15,7 +16,7 @@ import org.scalatest.funspec.AnyFunSpec
1516
import org.scalatest.matchers.should.Matchers
1617
import scala.reflect.io.Directory
1718

18-
class ChiselSimSpec extends AnyFunSpec with Matchers with ChiselSim with FileCheck with TestingDirectory {
19+
class ChiselSimSpec extends AnyFunSpec with Matchers with ChiselSim with FileCheck {
1920

2021
describe("scalatest.ChiselSim") {
2122

@@ -285,6 +286,95 @@ class ChiselSimSpec extends AnyFunSpec with Matchers with ChiselSim with FileChe
285286
}
286287

287288
}
289+
290+
class RandomizationTest extends Module {
291+
val in = IO(Input(UInt(32.W)))
292+
val addr = IO(Input(UInt(1.W)))
293+
val we = IO(Input(Bool()))
294+
295+
val r = IO(Output(UInt(32.W)))
296+
val cm = IO(Output(UInt(32.W)))
297+
val sm = IO(Output(UInt(32.W)))
298+
299+
private val reg = Reg(UInt(32.W))
300+
private val cmem = Mem(2, UInt(32.W))
301+
private val smem = SyncReadMem(2, UInt(32.W))
302+
303+
when(we) {
304+
reg :<= in
305+
cmem.write(addr, in)
306+
smem.write(addr, in)
307+
}
308+
309+
r :<= reg
310+
cm :<= cmem.read(addr)
311+
sm :<= smem.read(addr)
312+
}
313+
314+
it("should have randomization on by default") {
315+
316+
simulate(new RandomizationTest) { dut =>
317+
dut.clock.step(2)
318+
319+
val regValue = dut.r.peekValue().asBigInt
320+
info(s"register is not zero, has value $regValue")
321+
regValue should not be (0)
322+
323+
val cmemValue = dut.cm.peekValue().asBigInt
324+
info(s"combinational read memory index zero is not zero, has value $cmemValue")
325+
cmemValue should not be (0)
326+
327+
val smemValue = dut.sm.peekValue().asBigInt
328+
info(s"sequential read memory index zero is not zero, has value $smemValue")
329+
cmemValue should not be (0)
330+
}
331+
332+
}
333+
334+
it("uninitialized randomization should result in zeros (for Verilator)") {
335+
336+
simulate(new RandomizationTest, settings = Settings.default.copy(randomization = Randomization.uninitialized)) {
337+
dut =>
338+
dut.clock.step(2)
339+
340+
val regValue = dut.r.peekValue().asBigInt
341+
info(s"register is zero")
342+
regValue should be(0)
343+
344+
val cmemValue = dut.cm.peekValue().asBigInt
345+
info(s"combinational read memory index zero is zero")
346+
cmemValue should be(0)
347+
348+
val smemValue = dut.sm.peekValue().asBigInt
349+
info(s"sequential read memory index zero is zero")
350+
cmemValue should be(0)
351+
}
352+
353+
}
354+
355+
it("the randomization value should be user-overridable") {
356+
357+
simulate(
358+
new RandomizationTest,
359+
settings = Settings.default.copy(randomization = Randomization.random.copy(randomValue = Some("{32'd42}")))
360+
) { dut =>
361+
dut.clock.step(2)
362+
363+
val regValue = dut.r.peekValue().asBigInt
364+
info(s"register is 42")
365+
regValue should be(42)
366+
367+
val cmemValue = dut.cm.peekValue().asBigInt
368+
info(s"combinational read memory index zero is 42")
369+
cmemValue should be(42)
370+
371+
val smemValue = dut.sm.peekValue().asBigInt
372+
info(s"sequential read memory index zero is 42")
373+
cmemValue should be(42)
374+
}
375+
376+
}
377+
288378
}
289379

290380
describe("ChiselSim RunUntilSuccess stimulus") {

0 commit comments

Comments
 (0)