Skip to content

Add --dump-fir option to ChiselStage (backport #3453) #3455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 3.6.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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
)
)

}
15 changes: 15 additions & 0 deletions src/main/scala/circt/stage/CIRCTOptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,37 @@ 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 inputFile: Option[File] = None,
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(
inputFile: Option[File] = inputFile,
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(inputFile, 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))

}
21 changes: 21 additions & 0 deletions src/main/scala/circt/stage/ChiselStage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ import firrtl.{AnnotationSeq, EmittedVerilogCircuitAnnotation}
import firrtl.options.{Dependency, Phase, PhaseManager, Shell, Stage, StageMain}
import firrtl.stage.FirrtlCircuitAnnotation

<<<<<<< HEAD
=======
trait CLI { this: BareShell =>
parser.note("CIRCT (MLIR FIRRTL Compiler) options")
Seq(
CIRCTTargetAnnotation,
PreserveAggregate,
ChiselGeneratorAnnotation,
PrintFullStackTraceAnnotation,
ThrowOnFirstErrorAnnotation,
WarningsAsErrorsAnnotation,
WarningConfigurationAnnotation,
WarningConfigurationFileAnnotation,
SourceRootAnnotation,
SplitVerilog,
FirtoolBinaryPath,
DumpFir
).foreach(_.addOptions(parser))
}

>>>>>>> 4db86b2f8 (Add --dump-fir option to ChiselStage (#3453))
/** Entry point for running Chisel with the CIRCT compiler.
*
* This is intended to be a replacement for [[chisel3.stage.ChiselStage]].
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 @@ -28,6 +28,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
}
}
Expand Down
40 changes: 34 additions & 6 deletions src/main/scala/circt/stage/phases/CIRCT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ private[this] object Exceptions {
| https://github.com/llvm/circt/releases""".stripMargin
)
)
with NoStackTrace

}

Expand Down Expand Up @@ -191,20 +192,42 @@ 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")))

val circtAnnotationFilename = "circt.anno.json"

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 ++
Expand Down Expand Up @@ -241,16 +264,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 @@ -207,6 +207,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 @@ -1009,5 +1037,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())
}

}
}