-
Notifications
You must be signed in to change notification settings - Fork 597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce svsim
, a low level library for simulating SystemVerilog using Verilator and VCS.
#3121
Conversation
.devcontainer/Dockerfile
Outdated
@@ -2,6 +2,14 @@ FROM --platform=$BUILDPLATFORM ubuntu:20.04 | |||
|
|||
ARG TARGETARCH | |||
|
|||
# Initialize System |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes are PR-ed separately here: #3120. I'm expecting that PR to merge soon and then these will be removed from this PR.
build.sbt
Outdated
} else { | ||
"-Werror" :: Nil | ||
} | ||
if (sys.props.contains("disableFatalWarnings")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like build.sbt
was not formatted with the other sources, so VSCode decided to format it for me. I can undo these changes if folks care but I don't think it is a bad thing to have this file formatted.
svsim/README.md
Outdated
* `svsim` simulations are also backend-independent, meaning that the same code launches and controls a simulation regardless of what backend it was compiled using. | ||
* `svsim` backends are declarative. This means a backend only controls which executable is invoked for compilation, and what arguments are provided to that executable (either via command line or environment variables). Backends may also provide arguments to the simulation, but may not have any other specialized logic. | ||
* Simulations are controlled using an ad-hoc protocol where commands are read from `stdin` and messages are written to `stdout`. This protocol is **not** meant to be an ABI, and can change freely between Chisel verisons. | ||
* Communication with the simulation is pipelined, so the Scala driver can send multiple commands before processing responses from the simulation. This allows `svsim` to effectively eliminate the overhead of communicating with the simulation without resorting to JNA or Scala-side caching. This optimization can, of course, be defeated if the driver code waits on every command, but we consider this an antipattern. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am curious: did you benchmark this? Is it actually as fast as calling into a C-library function directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to say the performance is "analogous". Across SiFive's test suite, svsim
performed a bit faster than chiseltest
(though I think the speedup was mostly due to not running any SFC at all, also storing all this stuff in maps for VCS is easy to outperform). There were some tests that slowed down a little, and these were tests which were repeatedly waiting on the value of a peek
(and not just using expect
, which pipelines nicely). Obviously a straight-up C function call is faster, but compiling and simulating even a small design dwarfs this performance cost pretty quickly. For many tests, there are less than 10 "commands" sent to the simulation (svsim
implements TICK as a single command) so this overhead is negligible if you are not blocking on the IO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. That makes sense. So I guess most of the SiFive tests are BasicTester
style where they only wait for a stop
or a failed assert
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are peek poke tests as well. As long as they don't have a data dependency (i.e. they only use expect and not peek) the performance is analogous (and maybe faster if it is VCS due to the map issue above)
@jackkoenig This test failure is confusing, could you provide any insight? https://github.com/chipsalliance/chisel/actions/runs/4473691082/jobs/7861373685?pr=3121#step:7:2576 It doesn't seem related to anything I changed, but I don't see anything that would cause it to flake and that |
.devcontainer/Dockerfile
Outdated
--target install-firtool | ||
|
||
# Use VSCode for writing commit messages | ||
ENV GIT_EDITOR="code --wait" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this necessary for this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, these are broken out in a separate PR I'm hoping to merge soon: #3120
.devcontainer/Dockerfile
Outdated
@@ -25,8 +33,9 @@ RUN \ | |||
# Install GraalVM | |||
# This downloads all relevant GraalVM architectures at once, mostly because $TARGETARCH values don't map exactly to the release URLs. Since we're optimizing for developer experience here and not image size, this is OK | |||
# GraalVM release links can be found here: https://github.com/graalvm/graalvm-ce-builds/releases | |||
ADD https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.1/graalvm-ce-java17-linux-aarch64-22.3.1.tar.gz /graalvm/tarballs/arm64.tar.gz | |||
ADD https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-22.3.1/graalvm-ce-java17-linux-amd64-22.3.1.tar.gz /graalvm/tarballs/amd64.tar.gz | |||
ENV JAVA_VERSION=java11 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are we going from java17 to java11?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Java17 fails to compile what remains of FIRRTL. I didn't dig into this too deeply. Also worth calling out this is just for development. None of these are affecting the overall requirements of the Chisel project.
@@ -334,98 +347,101 @@ lazy val chisel = (project in file(".")) | |||
.dependsOn(macros) | |||
.dependsOn(core) | |||
.dependsOn(firrtl) | |||
.aggregate(macros, core, plugin, firrtl) | |||
.dependsOn(svsim) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does chisel the project depend on svsim? Shouldn't this go in the chisel test project dependencies, if anywhere today?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(also I don't really understand how this file works, so my question may be nonsensical)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm looking at chisel.testers
to host the "glue svsim to Chisel code". This is part of the chisel project AFAICT, but I may be missing something about test vs non-test dependencies.
build.sbt
Outdated
.settings(warningSuppression: _*) | ||
.settings(fatalWarningsSettings: _*) | ||
.settings(chiselSettings: _*) | ||
.settings(usePluginSettings: _*) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we are going to format this file in this PR, can we make sure that the process is repeatable. What settings was vscode picking up? I guess I'd strongly prefer we make the "format the build.sbt file and make sure it stays formatted" in its own PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe these are the settings from the project. I would guess the issue is this file doesn't have a ".scala" file extension so its not picked up in CI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree we should make this repeatable, looking in to how we can apply it to the build.sbt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a command scalafmtSbt
which will format *.sbt
files and project/*.scala
files. We should see if we can make this part of "all". I will say, we should do this as a separate PR rather than as part of this one just to keep the history more clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've created a PR to format the SBT files: #3125
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @jackkoenig!
svsim/README.md
Outdated
|
||
### Workspace | ||
|
||
The `Workspace` manages all interaction with the filesystem. Its API is mutable, because the underlying representation (files on disk) is mutable. A `Workspace` is represented on the filesystem as a folder. The `reset()` method will delete any previous state and create the necessary folders. The `elaborate()` method takes a `ModuleInfo` which describes the SystemVerilog module to be simulated. The idea is that a higher-level framework will provide specialized `elaborate` methods which emit SystemVerilog to `primarySourcesPath`, similar to what we do with `elaborateGCD()` in `src/test/scala/Resources.scala`. For example, Chisel can provide an `elaborate[T <: RawModule](module: => T)` method. `generateAdditionalSources()` uses the `ModuleInfo` from `elaborate` and generates the test harness. Finally, `compile()` uses a `Backend` to compile the simulation. Because `svsim`'s test harness is backend-independent, `compile` may be called multiple times with different settings or different backends to create multiple `Simulation`s (a `workingDirectoryTag` is provided so that mutliple simulations are output to separate directories). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit can we wrap this file either at 80 characters or at punctuation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is a Workspace
thread-safe or should it only ever be called from a single thread?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a lint/format pass we can do to add this? My IDE just auto-wraps, but I can also do this manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is a Workspace thread-safe or should it only ever be called from a single thread?
It is not explicitly thread safe, though you could theoretically run multiple compile
invocations on multiple threads. Not sure why you would want to do that though. Simulation
could be made thread-safe but currently the simulation logs would bork each other. I'd be interested in exploring this more if we ended up with a concrete use case that we want to support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we just have to tell the .scalafmt.conf to target files with sbt extension as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we just have to tell the .scalafmt.conf to target files with sbt extension as well
This is an md
file, and I don't see anything in the conf
related to file extensions. I also don't see an option to do this in VSCode (independent of scalafmt)... do folks just manually add returns? Seems onerous...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry didn't realize your question was for this thread.
I have my vscode to set the .md width to 80
svsim/README.md
Outdated
|
||
### `make replay` | ||
|
||
`svsim` by default emits an `execution-script.txt` when running a Simulation. This script captures all commands sent to the simulation, which enables `svsim` to add a second target to the `Makefile`: `make replay`. `make replay` rebuilds the simulation, picking up any changes as mentioned above, and then replays the commands that were sent by `Simulation.Controller` verbatim. This allows replaying most tests without having to re-run the Scala code, potentially with manual modifications to either the SystemVerilog or the test harness (though tests which modify their behavior based on values read from the `Simulation` may not be replayed faithfully). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we document the format of this replay file here? If not here, where is it documented? I see that it is not an ABI but it is a thing that if you're telling people they can edit we should at least point them to what the current format is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to specifically leave it un-documented (though simulation-driver.c
has some pretty explicit documentation) mostly because of the "This is not an ABI" point above, and folks shouldn't be using it across Chisel versions.
What looks like the failure isn't really the failure, the red there is coming from Chisel but that's an expected failure. If you scroll up on the logic you can see other similar examples. We really ought to fix Chisel's error reporting so that it doesn't show up here 😬 The real failure is in compilation of svsim above:
Allowing You can test this locally by setting the version of Scala in SBT shell with
|
Is Scala 2.12 something we care about for Chisel5? (Happy to fix the issue, but just asking) |
Yes, we're deprecating it in Chisel 5 but not removing till Chisel 6. |
d0edb39
to
5ff65e0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me (having also reviewed it before 🙂)!
5ff65e0
to
32e0669
Compare
Thanks @jackkoenig! Just to be clear, are you approving this to be merged or do you want someone else to also take a look? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally we reached a clean backend!
I just added some nitpick based on my experience. In the future we should consider these:
- provide
svsim
as a library, expose API withmainargs
, and can be reused by different build system, including make, mill, wake. - think about how to link the design patterns that consumes other libraries, e.g. spike, we need this for RISC-V cosim or BFM.
- provide a API to peek/poke/force internal signals, so that we can do fault injection and dynamic coverage.
case class ModuleInfo( | ||
name: String, | ||
ports: Seq[ModuleInfo.Port]) { | ||
private[svsim] val instanceName = "dut" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So basically, the svsim seems only being able to use the dut ports for communication, wondering how to provide force
or peek
to internal circuits in the future, this is extremely useful in fault injections. like ECC
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There will be an API that exposes probes/rwprobes as port-like things. This will be based on the reference types that are now in the FIRRTL 2.0.0 spec: chipsalliance/firrtl-spec#75
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@seldridge mentions the "correct" answer, but I also don't have a specific issue with adding more fields to ModuleInfo
if there is specific behavior we want from the test bench. Overall (and especially in these early days) I will have a strong bias towards simplicity, preferring solutions like the one @seldridge proposed over adding functionality to the test bench itself (I'll touch on this point in a separate response about how we generate the testbench below)
object Workspace { | ||
val testbenchModuleName: String = "svsimTestbench" | ||
} | ||
final class Workspace( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is possibly not a good idea to declare Workspace
Here. These is kind of pitfall that we should learn from: rocket-chip
did it in its tests generation, chiseltest force to use test_run_dir
, and use Scalatest of cause, I think decoupling function and build system will be neat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you take a second look, Workspace
explicitly does not couple this to Scalatest or any other system / paradigm. Its main role is to manage files on the filesystem (which are a requirement for VCS and Verilator). We enforce the internal structure (primary-sources
, generated-sources
, etc) for consistency, but this folder can be placed anywhere on the filesystem (for example, a temporary directory).
|
||
val systemVerilogTestbenchWriter = new LineWriter(s"$generatedSourcesPath/testbench.sv") | ||
try { | ||
val l = systemVerilogTestbenchWriter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future, can we use slang
to compose testbench like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe SV dialect in MLIR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for svsim
, having less dependencies and lower complexity gives a much bigger payoff than the additional functionality of something like slang
(which I haven't dug into deeply) or directly using CIRCT
would. This also helps create downward pressure on the complexity of the actual generated testbench. Finally, I like that this puts a floor the amount of "magic" that is done to connect the testbench to the dut, the process is entirely controlled by a list of strings and bools.
That said, this code is entirely contained in generateAdditionalSources
and there is nothing technically stopping us from having something like an alternate generateAdditionalSources(sourceGenerator: SourceGenerator)
where SourceGenerator
is a trait and can be implemented with whatever SystemVerilog generation mechanism the user wants.
systemVerilogTestbenchWriter.close() | ||
} | ||
|
||
val cDPIBridgeWriter = new LineWriter(s"$generatedSourcesPath/c-dpi-bridge.cpp") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hopefully we can have a better codegen solution, maybe LLVM IR? I'm not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I don't think the added complexity buys us anything here. Indeed, it would create a further disconnect between the actual C being generated and forces developers to start thinking "how do I get this codegen system to print the code I want?".
/** | ||
* Use the generated Makefile to compile the simulation, since this exercises the Makefile codepath and makes it less likely that we will break `make replay`. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kind of tricky... We can find a better solution to decouple the build system to leverage mill
or wake
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you mean here. mill
or wake
invoke Scala, and svsim
's role is to inside of a Scala process, compile generated Verilog into a simulation and then run that simulation. Also, as the comment states, the actual thing happening here is very simple (effectively just invoking vcs
or verilator
with a list of arguments). The only reason we add the indirection-via-makefile is to gain added confidence that the debugging flow (modifying either the invocation or the verilog and running make replay
) mirrors as accurately as possible the actual build flow.
} | ||
|
||
final object TraceSettings { | ||
final case class FsdbSettings(verdiHome: String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need APIs to configure VCS, e.g. SNPSLMD_LICENSE_FILE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The backends in svsim
(and specifically the VCS backend) are, at the moment, unapologetically "what happened to work for SiFive's use case". that said, because things are nicely organized it should be simple to augment these backends with whatever additional flags may be needed. My one request is that we have actual use cases when we add customization here, the VCS spec has a veritable encyclopedia of command-line arguments, some of which have almost programming-language-like logic that I don't think we need to mirror the entirety of here.
Also, I chose to upstream the VCS backend because I figured it would be helpful, but as we can't test it in the Chisel repo I'm not 100% sure that this is the best place for it. Once the dust settles, I'd be supportive of unsealing the Backend
trait so folks can just implement their own backend (which is a very simple task in svsim
). This could also be a good way to deal with different versions of VCS, etc. (wouldn't it be great if Synopsys just provided this!?)
s"$verdiHome/share/PLI/VCS/LINUX64/novas.tab", | ||
s"$verdiHome/share/PLI/VCS/LINUX64/pli.a" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't work with aarch64.
(Haha, we are considering purchase a bunch of Apple M1 with Asahi to run CI)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, as I mentioned above this was just "what works at SiFive". I'm very open to PRs cleaning this up and making it work for other environments (modulo the complexity and testability comments above). The best path forward here I see is to unseal the Backend
trait, have you guys iterate on your aarch64
config out-of-tree, and then upstream the changes that actually make it work. This has the added benefit of not requiring you to PR-and-bump chisel whenever you need to tweak a flag.
archOverride: Option[String] = None) | ||
|
||
def initializeFromProcessEnvironment() = { | ||
(sys.env.get("VCS_HOME"), sys.env.get("LM_LICENSE_FILE")) match { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK forgive me on my previous review.
However we shouldn't use LM_LICENSE_FILE
, that will introduce a huge problem in multi-EDA environment. please use SNPSLMD_LICENSE_FILE
instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what works for us (as far as I can tell we don't use SNPSLMD_LICENSE_FILE
). I think unsealing the Backend
trait, iterating out-of-tree and upstreaming is the right solution here.
The one design constraint I have is that we don't do any "magic" in svsim
and limit ourselves to grabbing what is in the environment and forwarding it to vcs
or verilator
.
disableFatalExitOnWarnings: Boolean = false) | ||
|
||
def initializeFromProcessEnvironment() = { | ||
val process = Runtime.getRuntime().exec(Array("which", "verilator")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe provide verilator
path in configuration will be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why? Our usual testing methodology is that users already have any tools (espresso, etc) on their path. I think we should be consistent here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe provide verilator path in configuration will be better?
You can create the backend directly using new verilator.Backend(executablePath = "/path/to/verilator")
Thanks for the detailed review @sequencer! One common thread I found was that maybe it makes sense to allow custom out-of-tree |
I apologize in advance to not-SiFive folks for ramboing a 2K+ line PR (the SiFive folks have had a bit more of a gradual introduction to this). You can be the final judge, but I think it is quite clean an readable and I will be covering some of the implementation details at the next Chisel Dev meeting (3/27) if folks are interested or have questions that don't work well in a PR context.
Type of Improvement
This upstreams the
svsim
project, which is a simple, self-contained Scala library for compiling SystemVerilog simulations using Verilator and VCS, and then executing and controlling those simulations. We builtsvsim
at SiFive to enable simulating MFC-emitted SystemVerilog in the absence of SFC.svsim
is meant to be low-level and not used directly by hardware designers. As such, its documentation is mainly in the code and insvsim/README.md
.A subsequent PR will migrate the
BasicTester
infrastructure to usesvsim
, and clean up that API so that users of Chisel have a built-in mechanism for running simple tests. We can then document how to usechisel3.testers
to enable simple unit testing of Chisel modules.Relation to
chiseltest
While
svsim
may draw comparisons tochiseltest
, I think it is more accurate to considersvsim
as a lower-level component that could be used to powerchiseltest
's simulation capabilities (svsim
is specifically not a testing framework). The "Design" section ofsvsim/README.md
discusses some of the thingschiseltest
does which are outside of the scope ofsvsim
.API Impact
This does not impact API.
Backend Code Generation Impact
This does not impact backend code generation.
Desired Merge Strategy
Squash
Release Notes
Added
svsim
, a new library for compiling and controlling SystemVerilog simulations in Scala using Verilator or VCS.Reviewer Checklist (only modified by reviewer)
3.5.x
or3.6.x
depending on impact, API modification or big change:5.0.0
)?Enable auto-merge (squash)
, clean up the commit message, and label withPlease Merge
.Create a merge commit
.