Skip to content

Commit 4ed2b52

Browse files
committed
[chiselsim] Add CLI option support
Add Scalatest integration for Chiselsim which allows users to write traits which add command line options to a Scalatest test. These hook into the `-D` Scalatest options (i.e., the ConfigMap) and allow the user to modify the common or backend-specific options of a test. This includes expected command line features: 1. Options have a name, help text, and a place to validate the option. 2. Illegal options are checked. 3. A help option is always supported. Add an example trait, `WaveformVCD` which will enable VCD dumping for _either_ Verilator or VCS. With this, you can then do, for a test which adds this trait: ./mill 'chisel3[2.13.16].test.testOnly' 'FooSpec' -- -DwithVcdCapability=true This is related to #4770. Signed-off-by: Schuyler Eldridge <[email protected]>
1 parent 75171dc commit 4ed2b52

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package chisel3.simulator.scalatest
4+
5+
import chisel3.testing.scalatest.HasConfigMap
6+
import firrtl.options.StageUtils.dramaticMessage
7+
import org.scalatest.TestSuite
8+
import scala.collection.mutable
9+
import scala.util.control.NoStackTrace
10+
import svsim.Backend.HarnessCompilationFlags.enableVcdTracingSupport
11+
import svsim.CommonCompilationSettings.VerilogPreprocessorDefine
12+
import svsim.{Backend, CommonCompilationSettings}
13+
14+
object HasCliArguments {
15+
16+
/** A ScalaTest command line option of the form `-D<name>=<value>`.
17+
*
18+
* @param name the name of the option
19+
* @param convert conver the `<value>` to the internal type `A`
20+
* @param updateCommonSettings a function to update the common compilation
21+
* settings
22+
* @param updateBackendSettings a function to update the backend-specific
23+
* compilation settings
24+
* @tparam the internal type of the option. This is what the `<value>` will
25+
* be converted to.
26+
*/
27+
case class CliOption[A](
28+
name: String,
29+
help: String,
30+
convert: (String) => A,
31+
updateCommonSettings: (A, CommonCompilationSettings) => CommonCompilationSettings,
32+
updateBackendSettings: (A, Backend.Settings) => Backend.Settings
33+
)
34+
35+
}
36+
37+
trait HasCliArguments extends HasConfigMap { this: TestSuite =>
38+
39+
import HasCliArguments._
40+
41+
private val options = mutable.HashMap.empty[String, CliOption[_]]
42+
43+
final def addOption(option: CliOption[_]): Unit = {
44+
if (options.contains(option.name))
45+
throw new Exception("unable to add option with name '$name' because this is already taken by another option")
46+
47+
options += option.name -> option
48+
}
49+
50+
private def helpBody = {
51+
val optionsHelp = options.map { case (_, option) =>
52+
s"""| ${option.name}
53+
| ${option.help}
54+
|""".stripMargin
55+
}.mkString
56+
s"""|Usage: <ScalaTest> [-D<name>=<value>...]
57+
|
58+
|This ChiselSim ScalaTest test supports passing command line arguments via
59+
|ScalaTest's "config map" feature. To access this, append `-D<name>=<value>` for
60+
|a legal option listed below.
61+
|
62+
|Options:
63+
|
64+
|$optionsHelp""".stripMargin
65+
}
66+
67+
private def illegalOptionCheck(): Unit = {
68+
configMap.keys.foreach { case name =>
69+
if (!options.contains(name)) {
70+
throw new IllegalArgumentException(
71+
dramaticMessage(
72+
header = Some(s"illegal ChiselSim ScalaTest option '$name'"),
73+
body = helpBody
74+
)
75+
) with NoStackTrace
76+
}
77+
}
78+
}
79+
80+
implicit def commonSettingsModifications: svsim.CommonSettingsModifications = (original: CommonCompilationSettings) =>
81+
{
82+
illegalOptionCheck()
83+
options.values.foldLeft(original) { case (acc, option) =>
84+
configMap.getOptional[String](option.name) match {
85+
case None => acc
86+
case Some(value) =>
87+
option.updateCommonSettings.apply(option.convert(value), acc)
88+
}
89+
}
90+
}
91+
92+
implicit def backendSettingsModifications: svsim.BackendSettingsModifications = (original: Backend.Settings) => {
93+
illegalOptionCheck()
94+
options.values.foldLeft(original) { case (acc, option) =>
95+
configMap.getOptional[String](option.name) match {
96+
case None => acc
97+
case Some(value) =>
98+
option.updateBackendSettings.apply(option.convert(value), acc)
99+
}
100+
}
101+
}
102+
103+
addOption(
104+
CliOption[Unit](
105+
name = "help",
106+
help = "display this help text",
107+
convert = _ => {
108+
throw new IllegalArgumentException(
109+
dramaticMessage(
110+
header = Some("help text requested"),
111+
body = helpBody
112+
)
113+
) with NoStackTrace
114+
},
115+
updateCommonSettings = (_, a) => a,
116+
updateBackendSettings = (_, a) => a
117+
)
118+
)
119+
120+
}
121+
122+
object CLI {
123+
124+
import HasCliArguments.CliOption
125+
126+
trait VcdCapability { this: HasCliArguments =>
127+
128+
addOption(
129+
CliOption[Unit](
130+
name = "withVcdCapability",
131+
help = "compiles the simulator with VCD support. (Use `enableWaves` to dump a VCD.)",
132+
convert = value => {
133+
val trueValue = Set("true", "1")
134+
trueValue.contains(value) match {
135+
case true => ()
136+
case false =>
137+
throw new IllegalArgumentException(
138+
s"""invalid argument '$value' for option 'enableVcdSupport', must be one of ${trueValue
139+
.mkString("[", ", ", "]")}"""
140+
) with NoStackTrace
141+
}
142+
},
143+
updateCommonSettings = (_, options) => {
144+
options.copy(verilogPreprocessorDefines =
145+
options.verilogPreprocessorDefines :+ VerilogPreprocessorDefine(enableVcdTracingSupport)
146+
)
147+
},
148+
updateBackendSettings = (_, options) =>
149+
options match {
150+
case options: svsim.vcs.Backend.CompilationSettings =>
151+
options.copy(
152+
traceSettings = options.traceSettings.copy(enableVcd = true)
153+
)
154+
case options: svsim.verilator.Backend.CompilationSettings =>
155+
options.copy(
156+
traceStyle = options.traceStyle match {
157+
case None => Some(svsim.verilator.Backend.CompilationSettings.TraceStyle.Vcd(filename = "trace.vcd"))
158+
case alreadySet => alreadySet
159+
}
160+
)
161+
}
162+
)
163+
)
164+
165+
}
166+
167+
}

0 commit comments

Comments
 (0)