diff --git a/src/main/scala/circt/stage/Annotations.scala b/src/main/scala/circt/stage/Annotations.scala index 9e2b7cefadc..36672d1db49 100644 --- a/src/main/scala/circt/stage/Annotations.scala +++ b/src/main/scala/circt/stage/Annotations.scala @@ -123,3 +123,17 @@ private[circt] case object SplitVerilog extends NoTargetAnnotation with CIRCTOpt ) } + +/** Write the intermediate `.fir` file in [[circt.stage.ChiselStage]] + */ +private[circt] case object DumpFir extends NoTargetAnnotation with CIRCTOption with HasShellOptions { + override def options = Seq( + new ShellOption[Unit]( + longOption = "dump-fir", + toAnnotationSeq = _ => Seq(this), + helpText = "Write the intermediate .fir file", + helpValueName = None + ) + ) + +} diff --git a/src/main/scala/circt/stage/CIRCTOptions.scala b/src/main/scala/circt/stage/CIRCTOptions.scala index 0bf2c3aa8a9..20a1851ac76 100644 --- a/src/main/scala/circt/stage/CIRCTOptions.scala +++ b/src/main/scala/circt/stage/CIRCTOptions.scala @@ -10,20 +10,24 @@ import java.io.File * @param outputFile the name of the file where the result will be written, if not split * @param preserveAggregate causes CIRCT to not lower aggregate FIRRTL IR types * @param target the specific IR or language target that CIRCT should compile to + * @param dumpFir dump the intermediate .fir artifact */ class CIRCTOptions private[stage] ( val outputFile: Option[File] = None, val preserveAggregate: Option[PreserveAggregate.Type] = None, val target: Option[CIRCTTarget.Type] = None, val firtoolOptions: Seq[String] = Seq.empty, - val splitVerilog: Boolean = false) { + val splitVerilog: Boolean = false, + val dumpFir: Boolean = false) { private[stage] def copy( outputFile: Option[File] = outputFile, preserveAggregate: Option[PreserveAggregate.Type] = preserveAggregate, target: Option[CIRCTTarget.Type] = target, firtoolOptions: Seq[String] = firtoolOptions, - splitVerilog: Boolean = splitVerilog - ): CIRCTOptions = new CIRCTOptions(outputFile, preserveAggregate, target, firtoolOptions, splitVerilog) + splitVerilog: Boolean = splitVerilog, + dumpFir: Boolean = dumpFir + ): CIRCTOptions = + new CIRCTOptions(outputFile, preserveAggregate, target, firtoolOptions, splitVerilog, dumpFir) } diff --git a/src/main/scala/circt/stage/ChiselStage.scala b/src/main/scala/circt/stage/ChiselStage.scala index 681ebd7fdf7..a99a842c7b9 100644 --- a/src/main/scala/circt/stage/ChiselStage.scala +++ b/src/main/scala/circt/stage/ChiselStage.scala @@ -41,7 +41,8 @@ trait CLI { this: BareShell => WarningConfigurationAnnotation, WarningConfigurationFileAnnotation, SourceRootAnnotation, - SplitVerilog + SplitVerilog, + DumpFir ).foreach(_.addOptions(parser)) } diff --git a/src/main/scala/circt/stage/package.scala b/src/main/scala/circt/stage/package.scala index c83e158b6ff..651b34eab7a 100644 --- a/src/main/scala/circt/stage/package.scala +++ b/src/main/scala/circt/stage/package.scala @@ -26,6 +26,7 @@ package object stage { case PreserveAggregate(a) => acc.copy(preserveAggregate = Some(a)) case FirtoolOption(a) => acc.copy(firtoolOptions = acc.firtoolOptions :+ a) case SplitVerilog => acc.copy(splitVerilog = true) + case DumpFir => acc.copy(dumpFir = true) case _ => acc } } diff --git a/src/main/scala/circt/stage/phases/CIRCT.scala b/src/main/scala/circt/stage/phases/CIRCT.scala index 01c880d3f8f..182d9d22534 100644 --- a/src/main/scala/circt/stage/phases/CIRCT.scala +++ b/src/main/scala/circt/stage/phases/CIRCT.scala @@ -119,6 +119,7 @@ private[this] object Exceptions { | https://github.com/llvm/circt/releases""".stripMargin ) ) + with NoStackTrace } @@ -181,11 +182,25 @@ class CIRCT extends Phase { /* Filter the annotations to only those things which CIRCT should see. */ (new WriteOutputAnnotations).transform(annotationsx) - val input: String = firrtlOptions.firrtlCircuit match { - case None => throw new OptionsException("No input file specified!") - case Some(circuit) => circuit.serialize + val (serialization: Iterable[String], circuitName: String) = firrtlOptions.firrtlCircuit match { + case None => throw new OptionsException("No input file specified!") + // TODO can we avoid converting, how else would we include filteredAnnos? + case Some(circuit) => + (firrtl.ir.Serializer.lazily(circuit), circuit.main) } + // FIRRTL is serialized either in memory or to a file + val input: Either[Iterable[String], os.Path] = + if (circtOptions.dumpFir) { + val td = os.Path(stageOptions.targetDir, os.pwd) + val filename = firrtlOptions.outputFileName.getOrElse(circuitName) + val firPath = td / s"$filename.fir" + os.write.over(firPath, serialization, createFolders = true) + Right(firPath) + } else { + Left(serialization) + } + val chiselAnnotationFilename: Option[String] = stageOptions.annotationFileOut.map(stageOptions.getBuildFileName(_, Some(".anno.json"))) @@ -193,8 +208,9 @@ class CIRCT extends Phase { val binary = "firtool" - val cmd = - Seq(binary, "-format=fir", "-warn-on-unprocessed-annotations", "-dedup") ++ + val cmd = // Only 1 of input or firFile will be Some + Seq(binary, input.fold(_ => "-format=fir", _.toString)) ++ + Seq("-warn-on-unprocessed-annotations", "-dedup") ++ Seq("-output-annotation-file", circtAnnotationFilename) ++ circtOptions.firtoolOptions ++ logLevel.toCIRCTOptions ++ @@ -231,16 +247,21 @@ class CIRCT extends Phase { ) }) - logger.info(s"""Running CIRCT: '${cmd.mkString(" ")} < $$input'""") + logger.info(s"""Running CIRCT: '${cmd.mkString(" ")}""" + input.fold(_ => " < $$input'", _ => "'")) val stdoutStream, stderrStream = new java.io.ByteArrayOutputStream val stdoutWriter = new java.io.PrintWriter(stdoutStream) val stderrWriter = new java.io.PrintWriter(stderrStream) + val stdin: os.ProcessInput = input match { + case Left(it) => (it: os.Source) // Static cast to apply implicit conversion + case Right(_) => os.Pipe + } + val stdout = os.ProcessOutput.Readlines(stdoutWriter.println) + val stderr = os.ProcessOutput.Readlines(stderrWriter.println) val exitValue = try { - (cmd #< new java.io.ByteArrayInputStream(input.getBytes)) - .!(ProcessLogger(stdoutWriter.println, stderrWriter.println)) + os.proc(cmd).call(check = false, stdin = stdin, stdout = stdout, stderr = stderr).exitCode } catch { - case a: java.lang.RuntimeException if a.getMessage().startsWith("No exit code") => + case a: java.io.IOException if a.getMessage().startsWith("Cannot run program") => throw new Exceptions.FirtoolNotFound(binary) } stdoutWriter.close() diff --git a/src/test/scala/circtTests/stage/ChiselStageSpec.scala b/src/test/scala/circtTests/stage/ChiselStageSpec.scala index 837f8d45232..b5d66f48c15 100644 --- a/src/test/scala/circtTests/stage/ChiselStageSpec.scala +++ b/src/test/scala/circtTests/stage/ChiselStageSpec.scala @@ -220,6 +220,34 @@ class ChiselStageSpec extends AnyFunSpec with Matchers with chiselTests.Utils { } + it("should optionally emit .fir when compiling to SystemVerilog") { + + val targetDir = new File("test_run_dir/ChiselStageSpec") + + val args: Array[String] = Array( + "--target", + "systemverilog", + "--target-dir", + targetDir.toString, + "--dump-fir" + ) + + val expectedSV = new File(targetDir, "Foo.sv") + expectedSV.delete() + + val expectedFir = new File(targetDir, "Foo.fir") + expectedFir.delete() + + (new ChiselStage) + .execute(args, Seq(ChiselGeneratorAnnotation(() => new ChiselStageSpec.Foo))) + + info(s"'$expectedSV' exists") + expectedSV should (exist) + info(s"'$expectedFir' exists") + expectedFir should (exist) + + } + it("should support custom firtool options") { val targetDir = new File("test_run_dir/ChiselStageSpec") diff --git a/unipublish/src/main/mima-filters/5.0.0-RC2.backwards.excludes b/unipublish/src/main/mima-filters/5.0.0-RC2.backwards.excludes index 60a4dc95006..70c1551307f 100644 --- a/unipublish/src/main/mima-filters/5.0.0-RC2.backwards.excludes +++ b/unipublish/src/main/mima-filters/5.0.0-RC2.backwards.excludes @@ -8,3 +8,4 @@ ProblemFilters.exclude[ReversedMissingMethodProblem]("chisel3.experimental.Sourc ProblemFilters.exclude[DirectMissingMethodProblem]("chisel3.internal.ErrorEntry.serialize") # Constructor is package private ProblemFilters.exclude[IncompatibleMethTypeProblem]("chisel3.stage.ChiselOptions.this") +ProblemFilters.exclude[DirectMissingMethodProblem]("circt.stage.CIRCTOptions.this")