diff --git a/build.mill b/build.mill index 1f3de48..32f1372 100644 --- a/build.mill +++ b/build.mill @@ -21,7 +21,7 @@ object `package` extends ChipModule { mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}", ) - object test extends ScalaTests, TestModule.ScalaTest { + object test extends ScalaTests, TestModule.ScalaTest, FreshForkPath { def moduleDeps = super.moduleDeps ++ Seq(testchipip) def mvnDeps = Seq( @@ -124,7 +124,7 @@ object `package` extends ChipModule { mvn"org.chipsalliance:::chisel-plugin:${chiselVersion}", ) - object test extends ScalaTests, TestModule.ScalaTest { + object test extends ScalaTests, TestModule.ScalaTest, FreshForkPath { def mvnDeps = Seq( mvn"org.scalatest::scalatest:3.2.19", ) diff --git a/module.mill b/module.mill index 4d59ba5..9201009 100644 --- a/module.mill +++ b/module.mill @@ -3,8 +3,22 @@ package build import mill._ import scalalib._ -trait ChipModule extends ScalaModule { +trait ChipModule extends ScalaModule, FreshForkPath { def scalaVersion = "2.13.18" def chiselVersion = "7.9.0" } +/** Mill's `javaHomePathForkEnv` reads `Task.env` inside a cached task, so a + * forked JVM (tests, `run`, `runMain`) keeps the PATH from the first run + * forever. Re-read it on every run instead: tools like espresso are found + * through it. Test objects don't inherit this from their parent module, so + * each one mixes it in. + */ +trait FreshForkPath extends javalib.RunModule { + def shellPath = Task.Input { Task.env.find(_._1.equalsIgnoreCase("PATH")).map(_._2) } + + override def javaHomePathForkEnv = Task { + val javaHomeBin = (javaHome().fold(os.Path(sys.props("java.home")))(_.path) / "bin").toString + Map("PATH" -> (javaHomeBin +: shellPath().toSeq).mkString(java.io.File.pathSeparator)) + } +} diff --git a/src/Iris.scala b/src/Iris.scala index 8b3adc0..c0d832f 100644 --- a/src/Iris.scala +++ b/src/Iris.scala @@ -52,7 +52,33 @@ class IrisSystem(implicit p: Parameters) with testchipip.serdes.CanHavePeripheryTLSerial with sifive.blocks.devices.uart.HasPeripheryUART with edu.berkeley.cs.chippy.clocking.HasChippyPRCI - with constellation.soc.CanHaveGlobalNoC + with constellation.soc.CanHaveGlobalNoC { + Espresso.requireOnPath() +} + +/** Constellation's route computers minimize their routing tables with Chisel's + * `decoder`, which quietly falls back to Quine-McCluskey when it cannot launch + * espresso. On the Iris NoC that fallback runs for a long time and then dies + * inside RouteComputer with "Vector cannot have negative size -2147483648", so + * fail before elaboration instead. + */ +object Espresso { + private lazy val check: Unit = + try os.proc("espresso").call(stdin = ".i 1\n.o 1\n.type fr\n1 1\n.e\n") + catch { + // The same failure that makes Chisel's decoder fall back to QMC. + case e: java.io.IOException => + throw new IllegalStateException( + s"""Cannot run espresso (${e.getMessage}). + |Iris needs espresso on PATH to generate its NoC: without it, + |Chisel's decoder falls back to Quine-McCluskey, which blows up on + |the route computers. Install it from + |https://github.com/chipsalliance/espresso (see README.md).""".stripMargin + ) + } + + def requireOnPath(): Unit = check +} class IrisTop(implicit p: Parameters) extends LazyModule with BindingScope { val system = LazyModule(new IrisSystem)