Skip to content

Commit

Permalink
Add --dump-fir option to ChiselStage (#3453)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jackkoenig committed Aug 3, 2023
1 parent fe4f130 commit 4db86b2
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 13 deletions.
14 changes: 14 additions & 0 deletions src/main/scala/circt/stage/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,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
)
)

}
9 changes: 6 additions & 3 deletions src/main/scala/circt/stage/CIRCTOptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,26 @@ 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 firtoolBinaryPath: Option[String] = None) {
val firtoolBinaryPath: Option[String] = None,
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,
firtoolBinaryPath: Option[String] = firtoolBinaryPath
firtoolBinaryPath: Option[String] = firtoolBinaryPath,
dumpFir: Boolean = dumpFir
): CIRCTOptions =
new CIRCTOptions(outputFile, preserveAggregate, target, firtoolOptions, splitVerilog, firtoolBinaryPath)
new CIRCTOptions(outputFile, preserveAggregate, target, firtoolOptions, splitVerilog, firtoolBinaryPath, dumpFir)

}
3 changes: 2 additions & 1 deletion src/main/scala/circt/stage/ChiselStage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ trait CLI { this: BareShell =>
WarningConfigurationFileAnnotation,
SourceRootAnnotation,
SplitVerilog,
FirtoolBinaryPath
FirtoolBinaryPath,
DumpFir
).foreach(_.addOptions(parser))
}

Expand Down
1 change: 1 addition & 0 deletions src/main/scala/circt/stage/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ package object stage {
case FirtoolBinaryPath(a) => acc.copy(firtoolBinaryPath = 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
}
}
Expand Down
40 changes: 31 additions & 9 deletions src/main/scala/circt/stage/phases/CIRCT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ private[this] object Exceptions {
| https://github.com/llvm/circt/releases""".stripMargin
)
)
with NoStackTrace

}

Expand Down Expand Up @@ -175,20 +176,36 @@ class CIRCT extends Phase {
case a => Some(a)
}

val input: String = firrtlOptions.firrtlCircuit match {
case None => throw new OptionsException("No input file specified!")
case Some(circuit) => CircuitWithAnnos(circuit = circuit, annotations = filteredAnnotations).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)
}

// 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")))

val circtAnnotationFilename = "circt.anno.json"

val binary = circtOptions.firtoolBinaryPath.getOrElse("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 ++
Expand Down Expand Up @@ -223,16 +240,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()
Expand Down
42 changes: 42 additions & 0 deletions src/test/scala/circtTests/stage/ChiselStageSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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())
}

}
}

0 comments on commit 4db86b2

Please sign in to comment.