Skip to content

Allow ChiselSim to link in prebuilt software libraries#5189

Merged
fabianschuiki merged 1 commit intomainfrom
fschuiki/chiselsim-libs
Jan 30, 2026
Merged

Allow ChiselSim to link in prebuilt software libraries#5189
fabianschuiki merged 1 commit intomainfrom
fschuiki/chiselsim-libs

Conversation

@fabianschuiki
Copy link
Contributor

@fabianschuiki fabianschuiki commented Jan 29, 2026

Add the libraries and libraryPaths settings to ChiselSim's Settings class that can be passed to simulate(...). These settings allow the user to provide a list of library names or paths that the simulator backend should link into the simulator. I envision this to be used for DPI-based tests to link in implementations for DPI functions. The build system can use the CHISELSIM_LIBS environment variable or the chiselsim.libraries Java property to provide a list of library names and their paths which the user can call out in libraries. This allows the Chisel code to not hardcode filesystem paths that are likely to be a build system concern, and instead only call out libraries by name.

This commit also adds a small C compiler invocation to build.mill in order to build a handful of shared libraries that the simulator unit tests will use to check the library linking behavior.

Contributor Checklist

  • Did you add Scaladoc to every public function/method?
  • Did you add at least one test demonstrating the PR?
  • Did you delete any extraneous printlns/debugging code?
  • Did you specify the type of improvement?
  • Did you add appropriate documentation in docs/src?
  • Did you request a desired merge strategy?
  • Did you add text to be included in the Release Notes for this change?

Type of Improvement

  • Feature (or new API)

Desired Merge Strategy

  • Squash: The PR will be squashed and merged (choose this if you have no preference).

Release Notes

Reviewer Checklist (only modified by reviewer)

  • Did you add the appropriate labels? (Select the most appropriate one based on the "Type of Improvement")
  • Did you mark the proper milestone (Bug fix: 3.6.x, 5.x, or 6.x depending on impact, API modification or big change: 7.0)?
  • Did you review?
  • Did you check whether all relevant Contributor checkboxes have been checked?
  • Did you do one of the following when ready to merge:
    • Squash: You/ the contributor Enable auto-merge (squash) and clean up the commit message.
    • Merge: Ensure that contributor has cleaned up their commit history, then merge with Create a merge commit.

@fabianschuiki fabianschuiki added the Feature New feature, will be included in release notes label Jan 29, 2026
@fabianschuiki
Copy link
Contributor Author

Not quite sure how to deal with the binary compatibility stuff 🤔

Comment on lines +274 to +279
entry.split("=", 2) match {
case Array(name, path) => name -> path
case _ =>
throw new IllegalArgumentException(
s"Invalid link library mapping `$entry`; expected `<name>=<path>`"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine but bonus points if you give an error with the bad input and a carat, e.g.

val filters = value.split(",")
filters.toList
// Add accumulating index to each filter for error reporting
.mapAccumulate(0) { case (idx, s) => (idx + 1 + s.length, (idx, s)) } // + 1 for removed ','
._2 // Discard accumulator
.map { case (idx, s) =>
WarningFilter.parse(s) match {
case Right(wf) => wf
case Left((jdx, msg)) =>
val carat = (" " * (idx + jdx)) + "^"
// Note tab before value and carat
throw new Exception(s"Failed to parse configuration: $msg\n $value\n $carat")
}
}
}

Comment on lines +284 to +285
val libraryMapFromProp = parseLibraryMap(sys.props.get("chiselsim.libraries"))
val libraryMapFromEnv = parseLibraryMap(sys.env.get("CHISELSIM_LIBS"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually these things are either or (and also either-or with being passed as arguments).

For example, you can set the CLASSPATH environment variable, but if you set it via -cp <classpath> that wins and the environment variable is ignored.

I'm not sure if we should do that here, but maybe? What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know about the classpath behavior. My gut feeling is that since these are lists, we might as well combine them instead of having a single library provided via one mechanism effectively remove the ones provided in the other. That feels somewhat unexpected from a user point of view.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intuition remains that overriding is more likely to be expected by users, but I'm also not going to sandbag on that because I agree that concatenation also feels reasonable.

val libraryMapFromEnv = parseLibraryMap(sys.env.get("CHISELSIM_LIBS"))
val libraryMap = libraryMapFromProp ++ libraryMapFromEnv
val resolvedLibraryPaths = settings.libraries.map { name =>
libraryMap.get(name) match {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused, so the libraries arguments are checked against the library mappings from the environment/system properties. What are the libraryPath arguments then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are a mechanism to provide a path in the Scala code directly. You'll likely not use that in practice, but there's no reason to not have that as a fallback option in case people work in some kind of fixed environment, or for testing purposes.

Comment on lines 340 to 373
// Compile shared libraries for the simulator link library tests.
def sharedTestLibs: T[Seq[os.Path]] = Task(persistent = true) {
val srcDir = BuildCtx.workspaceRoot / "src" / "test" / "resources" / "chisel3" / "simulator"
val cc = sys.env.getOrElse("CC", "cc")
val shared = if (scala.util.Properties.isMac) "-dynamiclib" else "-shared"
val srcFiles = Seq("linkLibA.c", "linkLibB.c", "linkLibC.c")
val libFiles = srcFiles.map { src =>
val lib = Task.dest / (src.stripSuffix(".c") + ".so")
os.proc(cc, shared, "-fPIC", srcDir / src, "-o", lib).call()
lib
}
libFiles
}

// Pass some of the pre-compiled shared libraries to the simulator via
// environment variables.
override def forkEnv = Task {
val libs = sharedTestLibs()
super.forkEnv() ++ Map(
// CHISELSIM_LIBS lets ChiselSim resolve libraries by name:
"CHISELSIM_LIBS" -> s"linkLibA=${libs(0)}",
// Also pass in a full path to test the `libraryPaths` setting:
"LINKLIBC_FULL_PATH" -> libs(2).toString
)
}

// Pass one of the pre-compiled shared libraries to the simulator via a JVM
// property.
override def forkArgs = Task {
val libs = sharedTestLibs()
super.forkArgs() :+ s"-Dchiselsim.libraries=linkLibB=${libs(1)}"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if instead of applying this to all chisel tests, we shouldn't add a new test build unit for this. Obviously they aren't harmful, but it feels a bit incongruous

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was wondering the same thing. I then thought that separating this out into a different build would complicate the whole build for no good practical reason, just aesthetics.

@fabianschuiki fabianschuiki force-pushed the fschuiki/chiselsim-libs branch from bff3724 to c3a7b43 Compare January 29, 2026 22:10
@fabianschuiki fabianschuiki force-pushed the fschuiki/chiselsim-libs branch 2 times, most recently from 547e3e2 to 789f854 Compare January 29, 2026 23:25
Add the `libraries` and `libraryPaths` settings to ChiselSim's
`Settings` class that can be passed to `simulate(...)`. These settings
allow the user to provide a list of library names or paths that the
simulator backend should link into the simulator. I envision this to be
used for DPI-based tests to link in implementations for DPI functions.
The build system can use the `CHISELSIM_LIBS` environment variable or
the `chiselsim.libraries` Java property to provide a list of library
names and their paths which the user can call out in `libraries`. This
allows the Chisel code to not hardcode filesystem paths that are likely
to be a build system concern, and instead only call out libraries by
name.

This commit also adds a small C compiler invocation to `build.mill` in
order to build a handful of shared libraries that the simulator unit
tests will use to check the library linking behavior.
@fabianschuiki fabianschuiki force-pushed the fschuiki/chiselsim-libs branch from 789f854 to cb0413e Compare January 30, 2026 16:40
@fabianschuiki fabianschuiki enabled auto-merge (squash) January 30, 2026 17:14
Copy link
Contributor

@jackkoenig jackkoenig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nits but I'm okay with merging

simulationSettings: CommonSimulationSettings = CommonSimulationSettings.default
simulationSettings: CommonSimulationSettings = CommonSimulationSettings.default,
// @unroll is required to maintain binary compatibility
@unroll linkLibraryPaths: Seq[String] = Seq.empty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the greatest discovery of my life.

Comment on lines +358 to 374
override def forkEnv = Task {
val libs = sharedTestLibs()
super.forkEnv() ++ Map(
// CHISELSIM_LIBS lets ChiselSim resolve libraries by name:
"CHISELSIM_LIBS" -> s"linkLibA=${libs(0)}",
// Also pass in a full path to test the `libraryPaths` setting:
"LINKLIBC_FULL_PATH" -> libs(2).toString
)
}

// Pass one of the pre-compiled shared libraries to the simulator via a JVM
// property.
override def forkArgs = Task {
val libs = sharedTestLibs()
super.forkArgs() :+ s"-Dchiselsim.libraries=linkLibB=${libs(1)}"
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not going to block but I don't love this meaning given to the various libs by index--I'd rather we used a case class (or even named tuple, <3 Scala 3) to give names to them, but I'll let it go.

@fabianschuiki fabianschuiki merged commit d8a0069 into main Jan 30, 2026
27 checks passed
@fabianschuiki fabianschuiki deleted the fschuiki/chiselsim-libs branch January 30, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature New feature, will be included in release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants