Skip to content

Commit 066fb47

Browse files
Add --dump-fir option to ChiselStage (backport #3453) (#3456)
* 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 4db86b2) # Conflicts: # src/main/scala/circt/stage/CIRCTOptions.scala # src/main/scala/circt/stage/ChiselStage.scala # src/main/scala/circt/stage/phases/CIRCT.scala * Resolve backport conflicts --------- Co-authored-by: Jack Koenig <[email protected]>
1 parent 1de1174 commit 066fb47

File tree

7 files changed

+83
-13
lines changed

7 files changed

+83
-13
lines changed

src/main/scala/circt/stage/Annotations.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,17 @@ private[circt] case object SplitVerilog extends NoTargetAnnotation with CIRCTOpt
123123
)
124124

125125
}
126+
127+
/** Write the intermediate `.fir` file in [[circt.stage.ChiselStage]]
128+
*/
129+
private[circt] case object DumpFir extends NoTargetAnnotation with CIRCTOption with HasShellOptions {
130+
override def options = Seq(
131+
new ShellOption[Unit](
132+
longOption = "dump-fir",
133+
toAnnotationSeq = _ => Seq(this),
134+
helpText = "Write the intermediate .fir file",
135+
helpValueName = None
136+
)
137+
)
138+
139+
}

src/main/scala/circt/stage/CIRCTOptions.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@ import java.io.File
1010
* @param outputFile the name of the file where the result will be written, if not split
1111
* @param preserveAggregate causes CIRCT to not lower aggregate FIRRTL IR types
1212
* @param target the specific IR or language target that CIRCT should compile to
13+
* @param dumpFir dump the intermediate .fir artifact
1314
*/
1415
class CIRCTOptions private[stage] (
1516
val outputFile: Option[File] = None,
1617
val preserveAggregate: Option[PreserveAggregate.Type] = None,
1718
val target: Option[CIRCTTarget.Type] = None,
1819
val firtoolOptions: Seq[String] = Seq.empty,
19-
val splitVerilog: Boolean = false) {
20+
val splitVerilog: Boolean = false,
21+
val dumpFir: Boolean = false) {
2022

2123
private[stage] def copy(
2224
outputFile: Option[File] = outputFile,
2325
preserveAggregate: Option[PreserveAggregate.Type] = preserveAggregate,
2426
target: Option[CIRCTTarget.Type] = target,
2527
firtoolOptions: Seq[String] = firtoolOptions,
26-
splitVerilog: Boolean = splitVerilog
27-
): CIRCTOptions = new CIRCTOptions(outputFile, preserveAggregate, target, firtoolOptions, splitVerilog)
28+
splitVerilog: Boolean = splitVerilog,
29+
dumpFir: Boolean = dumpFir
30+
): CIRCTOptions =
31+
new CIRCTOptions(outputFile, preserveAggregate, target, firtoolOptions, splitVerilog, dumpFir)
2832

2933
}

src/main/scala/circt/stage/ChiselStage.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ trait CLI { this: BareShell =>
4141
WarningConfigurationAnnotation,
4242
WarningConfigurationFileAnnotation,
4343
SourceRootAnnotation,
44-
SplitVerilog
44+
SplitVerilog,
45+
DumpFir
4546
).foreach(_.addOptions(parser))
4647
}
4748

src/main/scala/circt/stage/package.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ package object stage {
2626
case PreserveAggregate(a) => acc.copy(preserveAggregate = Some(a))
2727
case FirtoolOption(a) => acc.copy(firtoolOptions = acc.firtoolOptions :+ a)
2828
case SplitVerilog => acc.copy(splitVerilog = true)
29+
case DumpFir => acc.copy(dumpFir = true)
2930
case _ => acc
3031
}
3132
}

src/main/scala/circt/stage/phases/CIRCT.scala

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ private[this] object Exceptions {
119119
| https://github.com/llvm/circt/releases""".stripMargin
120120
)
121121
)
122+
with NoStackTrace
122123

123124
}
124125

@@ -181,20 +182,35 @@ class CIRCT extends Phase {
181182
/* Filter the annotations to only those things which CIRCT should see. */
182183
(new WriteOutputAnnotations).transform(annotationsx)
183184

184-
val input: String = firrtlOptions.firrtlCircuit match {
185-
case None => throw new OptionsException("No input file specified!")
186-
case Some(circuit) => circuit.serialize
185+
val (serialization: Iterable[String], circuitName: String) = firrtlOptions.firrtlCircuit match {
186+
case None => throw new OptionsException("No input file specified!")
187+
// TODO can we avoid converting, how else would we include filteredAnnos?
188+
case Some(circuit) =>
189+
(firrtl.ir.Serializer.lazily(circuit), circuit.main)
187190
}
188191

192+
// FIRRTL is serialized either in memory or to a file
193+
val input: Either[Iterable[String], os.Path] =
194+
if (circtOptions.dumpFir) {
195+
val td = os.Path(stageOptions.targetDir, os.pwd)
196+
val filename = firrtlOptions.outputFileName.getOrElse(circuitName)
197+
val firPath = td / s"$filename.fir"
198+
os.write.over(firPath, serialization, createFolders = true)
199+
Right(firPath)
200+
} else {
201+
Left(serialization)
202+
}
203+
189204
val chiselAnnotationFilename: Option[String] =
190205
stageOptions.annotationFileOut.map(stageOptions.getBuildFileName(_, Some(".anno.json")))
191206

192207
val circtAnnotationFilename = "circt.anno.json"
193208

194209
val binary = "firtool"
195210

196-
val cmd =
197-
Seq(binary, "-format=fir", "-warn-on-unprocessed-annotations", "-dedup") ++
211+
val cmd = // Only 1 of input or firFile will be Some
212+
Seq(binary, input.fold(_ => "-format=fir", _.toString)) ++
213+
Seq("-warn-on-unprocessed-annotations", "-dedup") ++
198214
Seq("-output-annotation-file", circtAnnotationFilename) ++
199215
circtOptions.firtoolOptions ++
200216
logLevel.toCIRCTOptions ++
@@ -231,16 +247,21 @@ class CIRCT extends Phase {
231247
)
232248
})
233249

234-
logger.info(s"""Running CIRCT: '${cmd.mkString(" ")} < $$input'""")
250+
logger.info(s"""Running CIRCT: '${cmd.mkString(" ")}""" + input.fold(_ => " < $$input'", _ => "'"))
235251
val stdoutStream, stderrStream = new java.io.ByteArrayOutputStream
236252
val stdoutWriter = new java.io.PrintWriter(stdoutStream)
237253
val stderrWriter = new java.io.PrintWriter(stderrStream)
254+
val stdin: os.ProcessInput = input match {
255+
case Left(it) => (it: os.Source) // Static cast to apply implicit conversion
256+
case Right(_) => os.Pipe
257+
}
258+
val stdout = os.ProcessOutput.Readlines(stdoutWriter.println)
259+
val stderr = os.ProcessOutput.Readlines(stderrWriter.println)
238260
val exitValue =
239261
try {
240-
(cmd #< new java.io.ByteArrayInputStream(input.getBytes))
241-
.!(ProcessLogger(stdoutWriter.println, stderrWriter.println))
262+
os.proc(cmd).call(check = false, stdin = stdin, stdout = stdout, stderr = stderr).exitCode
242263
} catch {
243-
case a: java.lang.RuntimeException if a.getMessage().startsWith("No exit code") =>
264+
case a: java.io.IOException if a.getMessage().startsWith("Cannot run program") =>
244265
throw new Exceptions.FirtoolNotFound(binary)
245266
}
246267
stdoutWriter.close()

src/test/scala/circtTests/stage/ChiselStageSpec.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,34 @@ class ChiselStageSpec extends AnyFunSpec with Matchers with chiselTests.Utils {
220220

221221
}
222222

223+
it("should optionally emit .fir when compiling to SystemVerilog") {
224+
225+
val targetDir = new File("test_run_dir/ChiselStageSpec")
226+
227+
val args: Array[String] = Array(
228+
"--target",
229+
"systemverilog",
230+
"--target-dir",
231+
targetDir.toString,
232+
"--dump-fir"
233+
)
234+
235+
val expectedSV = new File(targetDir, "Foo.sv")
236+
expectedSV.delete()
237+
238+
val expectedFir = new File(targetDir, "Foo.fir")
239+
expectedFir.delete()
240+
241+
(new ChiselStage)
242+
.execute(args, Seq(ChiselGeneratorAnnotation(() => new ChiselStageSpec.Foo)))
243+
244+
info(s"'$expectedSV' exists")
245+
expectedSV should (exist)
246+
info(s"'$expectedFir' exists")
247+
expectedFir should (exist)
248+
249+
}
250+
223251
it("should support custom firtool options") {
224252
val targetDir = new File("test_run_dir/ChiselStageSpec")
225253

unipublish/src/main/mima-filters/5.0.0-RC2.backwards.excludes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ ProblemFilters.exclude[ReversedMissingMethodProblem]("chisel3.experimental.Sourc
88
ProblemFilters.exclude[DirectMissingMethodProblem]("chisel3.internal.ErrorEntry.serialize")
99
# Constructor is package private
1010
ProblemFilters.exclude[IncompatibleMethTypeProblem]("chisel3.stage.ChiselOptions.this")
11+
ProblemFilters.exclude[DirectMissingMethodProblem]("circt.stage.CIRCTOptions.this")

0 commit comments

Comments
 (0)