From 4c858bdc153a5f729096583cc23d3c2d54806233 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 3 Aug 2023 10:16:25 -0700 Subject: [PATCH] Add --dump-fir option to ChiselStage (#3453) This option will dump the .fir before invoking firtool. Additional changes: * Use os.lib for invoking firtool * Use lazy serialization to avoid holding the entire FIRRTL in memory. * Mix NoStackTrace into FirtoolNotFound * Fix detection of no firtool (cherry picked from commit 4db86b2f8bc5f7cb02b2e7097a29bf9c81cc86a7) # Conflicts: # src/main/scala/circt/stage/CIRCTOptions.scala # src/main/scala/circt/stage/ChiselStage.scala # src/main/scala/circt/stage/phases/CIRCT.scala --- src/main/scala/circt/stage/Annotations.scala | 14 +++++++ src/main/scala/circt/stage/CIRCTOptions.scala | 15 +++++++ src/main/scala/circt/stage/ChiselStage.scala | 6 +++ src/main/scala/circt/stage/package.scala | 1 + src/main/scala/circt/stage/phases/CIRCT.scala | 40 +++++++++++++++--- .../circtTests/stage/ChiselStageSpec.scala | 42 +++++++++++++++++++ 6 files changed, 112 insertions(+), 6 deletions(-) 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..b9fb6202269 100644 --- a/src/main/scala/circt/stage/CIRCTOptions.scala +++ b/src/main/scala/circt/stage/CIRCTOptions.scala @@ -10,20 +10,35 @@ 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, +<<<<<<< HEAD val splitVerilog: Boolean = false) { +======= + val splitVerilog: Boolean = false, + val firtoolBinaryPath: Option[String] = None, + val dumpFir: Boolean = false) { +>>>>>>> 4db86b2f8 (Add --dump-fir option to ChiselStage (#3453)) private[stage] def copy( outputFile: Option[File] = outputFile, preserveAggregate: Option[PreserveAggregate.Type] = preserveAggregate, target: Option[CIRCTTarget.Type] = target, firtoolOptions: Seq[String] = firtoolOptions, +<<<<<<< HEAD splitVerilog: Boolean = splitVerilog ): CIRCTOptions = new CIRCTOptions(outputFile, preserveAggregate, target, firtoolOptions, splitVerilog) +======= + splitVerilog: Boolean = splitVerilog, + firtoolBinaryPath: Option[String] = firtoolBinaryPath, + dumpFir: Boolean = dumpFir + ): CIRCTOptions = + new CIRCTOptions(outputFile, preserveAggregate, target, firtoolOptions, splitVerilog, firtoolBinaryPath, dumpFir) +>>>>>>> 4db86b2f8 (Add --dump-fir option to ChiselStage (#3453)) } diff --git a/src/main/scala/circt/stage/ChiselStage.scala b/src/main/scala/circt/stage/ChiselStage.scala index 681ebd7fdf7..8e49f37f436 100644 --- a/src/main/scala/circt/stage/ChiselStage.scala +++ b/src/main/scala/circt/stage/ChiselStage.scala @@ -41,7 +41,13 @@ trait CLI { this: BareShell => WarningConfigurationAnnotation, WarningConfigurationFileAnnotation, SourceRootAnnotation, +<<<<<<< HEAD SplitVerilog +======= + SplitVerilog, + FirtoolBinaryPath, + DumpFir +>>>>>>> 4db86b2f8 (Add --dump-fir option to ChiselStage (#3453)) ).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..52a1ab19bbe 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,32 @@ class CIRCT extends Phase { /* Filter the annotations to only those things which CIRCT should see. */ (new WriteOutputAnnotations).transform(annotationsx) +<<<<<<< HEAD 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) => + val cwa = CircuitWithAnnos(circuit = circuit, annotations = filteredAnnotations) + (firrtl.ir.Serializer.lazily(cwa), circuit.main) +>>>>>>> 4db86b2f8 (Add --dump-fir option to ChiselStage (#3453)) } + // 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 +215,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 +254,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..80026424da3 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") @@ -1077,5 +1105,19 @@ class ChiselStageSpec extends AnyFunSpec with Matchers with chiselTests.Utils { exception.getStackTrace should be(Array()) } + it("should report a specific error if firtool is not found on the PATH") { + val exception = intercept[Exception] { + ChiselStage.emitSystemVerilog(new ChiselStageSpec.Foo, Array("--firtool-binary-path", "potato")) + } + + info("The exception includes a useful error message") + val message = exception.getMessage + message should include("potato not found") + message should include("Chisel requires that firtool, the MLIR-based FIRRTL Compiler (MFC), is installed") + + info("The exception should not contain a stack trace") + exception.getStackTrace should be(Array()) + } + } }