aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/scala/firrtl/Emitter.scala53
-rw-r--r--src/main/scala/firrtl/options/StageAnnotations.scala60
-rw-r--r--src/main/scala/firrtl/options/StageOptions.scala10
-rw-r--r--src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala41
-rw-r--r--src/main/scala/firrtl/stage/FirrtlStage.scala3
-rw-r--r--src/main/scala/firrtl/stage/package.scala4
-rw-r--r--src/main/scala/firrtl/stage/phases/Compiler.scala2
-rw-r--r--src/main/scala/firrtl/stage/phases/WriteEmitted.scala2
-rw-r--r--src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala2
-rw-r--r--src/test/scala/firrtl/testutils/FirrtlSpec.scala6
-rw-r--r--src/test/scala/firrtlTests/execution/VerilogExecution.scala3
-rw-r--r--src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala61
-rw-r--r--src/test/scala/firrtlTests/transforms/LegalizeReductions.scala2
13 files changed, 208 insertions, 41 deletions
diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala
index b8d26103..432fa59e 100644
--- a/src/main/scala/firrtl/Emitter.scala
+++ b/src/main/scala/firrtl/Emitter.scala
@@ -14,11 +14,14 @@ import firrtl.PrimOps._
import firrtl.WrappedExpression._
import Utils._
import MemPortUtils.{memPortField, memType}
-import firrtl.options.{HasShellOptions, PhaseException, ShellOption, Unserializable}
-import firrtl.stage.{RunFirrtlTransformAnnotation, TransformManager}
+import firrtl.options.{HasShellOptions, CustomFileEmission, ShellOption, PhaseException}
+import firrtl.options.Viewer.view
+import firrtl.stage.{FirrtlFileAnnotation, FirrtlOptions, RunFirrtlTransformAnnotation, TransformManager}
// Datastructures
import scala.collection.mutable.ArrayBuffer
+import java.io.File
+
case class EmitterException(message: String) extends PassException(message)
// ***** Annotations for telling the Emitters what to emit *****
@@ -92,17 +95,35 @@ final case class EmittedFirrtlModule(name: String, value: String, outputSuffix:
final case class EmittedVerilogModule(name: String, value: String, outputSuffix: String) extends EmittedModule
/** Traits for Annotations containing emitted components */
-sealed trait EmittedAnnotation[T <: EmittedComponent] extends NoTargetAnnotation with Unserializable {
+sealed trait EmittedAnnotation[T <: EmittedComponent] extends NoTargetAnnotation with CustomFileEmission {
val value: T
+
+ override protected def baseFileName(annotations: AnnotationSeq): String = {
+ view[FirrtlOptions](annotations).outputFileName.getOrElse(value.name)
+ }
+
+ override protected val suffix: Option[String] = Some(value.outputSuffix)
+
+}
+sealed trait EmittedCircuitAnnotation[T <: EmittedCircuit] extends EmittedAnnotation[T] {
+
+ override def getBytes = value.value.getBytes
+
+}
+sealed trait EmittedModuleAnnotation[T <: EmittedModule] extends EmittedAnnotation[T] {
+
+ override def getBytes = value.value.getBytes
+
}
-sealed trait EmittedCircuitAnnotation[T <: EmittedCircuit] extends EmittedAnnotation[T]
-sealed trait EmittedModuleAnnotation[T <: EmittedModule] extends EmittedAnnotation[T]
case class EmittedFirrtlCircuitAnnotation(value: EmittedFirrtlCircuit)
- extends EmittedCircuitAnnotation[EmittedFirrtlCircuit]
+ extends EmittedCircuitAnnotation[EmittedFirrtlCircuit] {
+
+ override def replacements(file: File): AnnotationSeq = Seq(FirrtlFileAnnotation(file.toString))
+
+}
case class EmittedVerilogCircuitAnnotation(value: EmittedVerilogCircuit)
extends EmittedCircuitAnnotation[EmittedVerilogCircuit]
-
case class EmittedFirrtlModuleAnnotation(value: EmittedFirrtlModule)
extends EmittedModuleAnnotation[EmittedFirrtlModule]
case class EmittedVerilogModuleAnnotation(value: EmittedVerilogModule)
@@ -465,10 +486,10 @@ class VerilogEmitter extends SeqTransform with Emitter {
throw EmitterException("Cannot emit verification statements in Verilog" +
"(2001). Use the SystemVerilog emitter instead.")
}
-
- /**
+
+ /**
* Store Emission option per Target
- * Guarantee only one emission option per Target
+ * Guarantee only one emission option per Target
*/
private[firrtl] class EmissionOptionMap[V <: EmissionOption](val df : V) {
private val m = collection.mutable.HashMap[ReferenceTarget, V]().withDefaultValue(df)
@@ -480,9 +501,9 @@ class VerilogEmitter extends SeqTransform with Emitter {
}
def apply(key: ReferenceTarget): V = m.apply(key)
}
-
+
/** Provide API to retrieve EmissionOptions based on the provided [[AnnotationSeq]]
- *
+ *
* @param annotations : AnnotationSeq to be searched for EmissionOptions
*
*/
@@ -500,16 +521,16 @@ class VerilogEmitter extends SeqTransform with Emitter {
def getRegisterEmissionOption(target: ReferenceTarget): RegisterEmissionOption =
registerEmissionOption(target)
-
+
def getWireEmissionOption(target: ReferenceTarget): WireEmissionOption =
wireEmissionOption(target)
-
+
def getPortEmissionOption(target: ReferenceTarget): PortEmissionOption =
portEmissionOption(target)
-
+
def getNodeEmissionOption(target: ReferenceTarget): NodeEmissionOption =
nodeEmissionOption(target)
-
+
def getConnectEmissionOption(target: ReferenceTarget): ConnectEmissionOption =
connectEmissionOption(target)
diff --git a/src/main/scala/firrtl/options/StageAnnotations.scala b/src/main/scala/firrtl/options/StageAnnotations.scala
index 51d1c268..32f8ff59 100644
--- a/src/main/scala/firrtl/options/StageAnnotations.scala
+++ b/src/main/scala/firrtl/options/StageAnnotations.scala
@@ -4,6 +4,9 @@ package firrtl.options
import firrtl.AnnotationSeq
import firrtl.annotations.{Annotation, NoTargetAnnotation}
+import firrtl.options.Viewer.view
+
+import java.io.File
import scopt.OptionParser
@@ -14,6 +17,63 @@ sealed trait StageOption { this: Annotation => }
*/
trait Unserializable { this: Annotation => }
+/** Mix-in that lets an [[firrtl.annotations.Annotation Annotation]] serialize itself to a file separate from the output
+ * annotation file.
+ *
+ * This can be used as a mechanism to serialize an [[firrtl.options.Unserializable Unserializable]] annotation or to
+ * write ancillary collateral used by downstream tooling, e.g., a TCL script or an FPGA constraints file. Any
+ * annotations using this mix-in will be serialized by the [[firrtl.options.phases.WriteOutputAnnotations
+ * WriteOutputAnnotations]] phase. This is one of the last phases common to all [[firrtl.options.Stage Stages]] and
+ * should not have to be called/included manually.
+ *
+ * Note: from the perspective of transforms generating annotations that mix-in this trait, the serialized files are not
+ * expected to be available to downstream transforms. Communication of information between transforms must occur
+ * through the annotations that will eventually be serialized to files.
+ */
+trait CustomFileEmission { this: Annotation =>
+
+ /** Output filename where serialized content will be written
+ *
+ * The full annotation sequence is a parameter to allow for the location where this annotation will be serialized to
+ * be a function of other annotations, e.g., if the location where information is written is controlled by a separate
+ * file location annotation.
+ *
+ * @param annotations the annotation sequence at the time of emission
+ */
+ protected def baseFileName(annotations: AnnotationSeq): String
+
+ /** Optional suffix of the output file */
+ protected def suffix: Option[String]
+
+ /** A method that can convert this annotation to bytes that will be written to a file.
+ *
+ * If you only need to serialize a string, you can use the `getBytes` method:
+ * {{{
+ * def getBytes: Iterable[Byte] = myString.getBytes
+ * }}}
+ */
+ def getBytes: Iterable[Byte]
+
+ /** Optionally, a sequence of annotations that will replace this annotation in the output annotation file.
+ *
+ * A non-empty implementation of this method is a mechanism for telling a downstream [[firrtl.options.Stage Stage]]
+ * how to deserialize the information that was serialized to a separate file. For example, if a FIRRTL circuit is
+ * serialized to a separate file, this method could include an input file annotation that a later stage can use to
+ * read the serialized FIRRTL circuit back in.
+ */
+ def replacements(file: File): AnnotationSeq = Seq.empty
+
+ /** Method that returns the filename where this annotation will be serialized.
+ *
+ * @param annotations the annotations at the time of serialization
+ */
+ final def filename(annotations: AnnotationSeq): File = {
+ val name = view[StageOptions](annotations).getBuildFileName(baseFileName(annotations), suffix)
+ new File(name)
+ }
+
+}
+
/** Holds the name of the target directory
* - set with `-td/--target-dir`
* - if unset, a [[TargetDirAnnotation]] will be generated with the
diff --git a/src/main/scala/firrtl/options/StageOptions.scala b/src/main/scala/firrtl/options/StageOptions.scala
index 7905799c..f60a991c 100644
--- a/src/main/scala/firrtl/options/StageOptions.scala
+++ b/src/main/scala/firrtl/options/StageOptions.scala
@@ -59,11 +59,13 @@ class StageOptions private [firrtl] (
} else {
new File(targetDir + "/" + f)
}
- }.getCanonicalFile
+ }.toPath.normalize.toFile
- val parent = file.getParentFile
-
- if (!parent.exists) { parent.mkdirs() }
+ file.getParentFile match {
+ case null =>
+ case parent if (!parent.exists) => parent.mkdirs()
+ case _ =>
+ }
file.toString
}
diff --git a/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala b/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala
index 7d857108..ccd08fa4 100644
--- a/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala
+++ b/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala
@@ -3,11 +3,12 @@
package firrtl.options.phases
import firrtl.AnnotationSeq
-import firrtl.annotations.{DeletedAnnotation, JsonProtocol}
-import firrtl.options.{Phase, StageOptions, Unserializable, Viewer}
-import firrtl.options.Dependency
+import firrtl.annotations.{Annotation, DeletedAnnotation, JsonProtocol}
+import firrtl.options.{CustomFileEmission, Dependency, Phase, PhaseException, StageOptions, Unserializable, Viewer}
-import java.io.PrintWriter
+import java.io.{BufferedWriter, File, FileWriter, PrintWriter}
+
+import scala.collection.mutable
/** [[firrtl.options.Phase Phase]] that writes an [[AnnotationSeq]] to a file. A file is written if and only if a
* [[StageOptions]] view has a non-empty [[StageOptions.annotationFileOut annotationFileOut]].
@@ -27,10 +28,34 @@ class WriteOutputAnnotations extends Phase {
/** Write the input [[AnnotationSeq]] to a fie. */
def transform(annotations: AnnotationSeq): AnnotationSeq = {
val sopts = Viewer[StageOptions].view(annotations)
- val serializable = annotations.filter{
- case _: Unserializable => false
- case _: DeletedAnnotation => sopts.writeDeleted
- case _ => true
+ val filesWritten = mutable.HashMap.empty[String, Annotation]
+ val serializable: AnnotationSeq = annotations.toSeq.flatMap {
+ case _: Unserializable => None
+ case a: DeletedAnnotation => if (sopts.writeDeleted) { Some(a) } else { None }
+ case a: CustomFileEmission =>
+ val filename = a.filename(annotations)
+ val canonical = filename.getCanonicalPath()
+
+ filesWritten.get(canonical) match {
+ case None =>
+ val w = new BufferedWriter(new FileWriter(filename))
+ a.getBytes.foreach( w.write(_) )
+ w.close()
+ filesWritten(canonical) = a
+ case Some(first) =>
+ val msg =
+ s"""|Multiple CustomFileEmission annotations would be serialized to the same file, '$canonical'
+ | - first writer:
+ | class: ${first.getClass.getName}
+ | trimmed serialization: ${first.serialize.take(80)}
+ | - second writer:
+ | class: ${a.getClass.getName}
+ | trimmed serialization: ${a.serialize.take(80)}
+ |""".stripMargin
+ throw new PhaseException(msg)
+ }
+ a.replacements(filename)
+ case a => Some(a)
}
sopts.annotationFileOut match {
diff --git a/src/main/scala/firrtl/stage/FirrtlStage.scala b/src/main/scala/firrtl/stage/FirrtlStage.scala
index d26c5cff..1042f979 100644
--- a/src/main/scala/firrtl/stage/FirrtlStage.scala
+++ b/src/main/scala/firrtl/stage/FirrtlStage.scala
@@ -8,8 +8,7 @@ import firrtl.options.phases.DeletedWrapper
import firrtl.stage.phases.CatchExceptions
class FirrtlPhase
- extends PhaseManager(targets=Seq(Dependency[firrtl.stage.phases.Compiler],
- Dependency[firrtl.stage.phases.WriteEmitted])) {
+ extends PhaseManager(targets=Seq(Dependency[firrtl.stage.phases.Compiler])) {
override def invalidates(a: Phase) = false
diff --git a/src/main/scala/firrtl/stage/package.scala b/src/main/scala/firrtl/stage/package.scala
index 5bb7378d..e9cf3fb4 100644
--- a/src/main/scala/firrtl/stage/package.scala
+++ b/src/main/scala/firrtl/stage/package.scala
@@ -39,11 +39,9 @@ package object stage {
private [firrtl] implicit object FirrtlExecutionResultView extends OptionsView[FirrtlExecutionResult] with LazyLogging {
- private lazy val dummyWriteEmitted = new WriteEmitted
-
def view(options: AnnotationSeq): FirrtlExecutionResult = {
val emittedRes = options
- .collect{ case DeletedAnnotation(dummyWriteEmitted.name, a: EmittedAnnotation[_]) => a.value.value }
+ .collect{ case a: EmittedAnnotation[_] => a.value.value }
.mkString("\n")
val emitters = options.collect{ case RunFirrtlTransformAnnotation(e: Emitter) => e }
diff --git a/src/main/scala/firrtl/stage/phases/Compiler.scala b/src/main/scala/firrtl/stage/phases/Compiler.scala
index b3e902c8..b73e3058 100644
--- a/src/main/scala/firrtl/stage/phases/Compiler.scala
+++ b/src/main/scala/firrtl/stage/phases/Compiler.scala
@@ -51,7 +51,7 @@ class Compiler extends Phase with Translator[AnnotationSeq, Seq[CompilerRun]] {
Dependency[AddCircuit],
Dependency[AddImplicitOutputFile])
- override def optionalPrerequisiteOf = Seq(Dependency[WriteEmitted])
+ override def optionalPrerequisiteOf = Seq.empty
override def invalidates(a: Phase) = false
diff --git a/src/main/scala/firrtl/stage/phases/WriteEmitted.scala b/src/main/scala/firrtl/stage/phases/WriteEmitted.scala
index 98557aca..e2db2a94 100644
--- a/src/main/scala/firrtl/stage/phases/WriteEmitted.scala
+++ b/src/main/scala/firrtl/stage/phases/WriteEmitted.scala
@@ -24,6 +24,8 @@ import java.io.PrintWriter
*
* Any annotations written to files will be deleted.
*/
+@deprecated("Annotations that mixin the CustomFileEmission trait are automatically serialized by stages." +
+ "This will be removed in FIRRTL 1.5", "FIRRTL 1.4.0")
class WriteEmitted extends Phase {
override def prerequisites = Seq.empty
diff --git a/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala b/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala
index 64654175..007608ca 100644
--- a/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala
+++ b/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala
@@ -125,7 +125,7 @@ class DriverCompatibilitySpec extends AnyFlatSpec with Matchers with PrivateMeth
new PhaseFixture(new AddImplicitFirrtlFile) {
val annotations = Seq( TopNameAnnotation("foo") )
val expected = annotations.toSet +
- FirrtlFileAnnotation(new File("foo.fir").getCanonicalPath)
+ FirrtlFileAnnotation(new File("foo.fir").getPath())
phase.transform(annotations).toSet should be (expected)
}
diff --git a/src/test/scala/firrtl/testutils/FirrtlSpec.scala b/src/test/scala/firrtl/testutils/FirrtlSpec.scala
index 75739147..dfc20352 100644
--- a/src/test/scala/firrtl/testutils/FirrtlSpec.scala
+++ b/src/test/scala/firrtl/testutils/FirrtlSpec.scala
@@ -104,13 +104,13 @@ trait FirrtlRunners extends BackendCompilationUtilities {
val customName = s"${prefix}_custom"
val customAnnos = getBaseAnnos(customName) ++: toAnnos((new GetNamespace) +: customTransforms) ++: customAnnotations
- val customResult = (new firrtl.stage.FirrtlStage).run(customAnnos)
+ val customResult = (new firrtl.stage.FirrtlStage).execute(Array.empty, customAnnos)
val nsAnno = customResult.collectFirst { case m: ModuleNamespaceAnnotation => m }.get
val refSuggestedName = s"${prefix}_ref"
val refAnnos = getBaseAnnos(refSuggestedName) ++: Seq(RunFirrtlTransformAnnotation(new RenameModules), nsAnno)
- val refResult = (new firrtl.stage.FirrtlStage).run(refAnnos)
+ val refResult = (new firrtl.stage.FirrtlStage).execute(Array.empty, refAnnos)
val refName = refResult.collectFirst({ case stage.FirrtlCircuitAnnotation(c) => c.main }).getOrElse(refSuggestedName)
assert(BackendCompilationUtilities.yosysExpectSuccess(customName, refName, testDir, timesteps))
@@ -145,7 +145,7 @@ trait FirrtlRunners extends BackendCompilationUtilities {
annotations ++:
(customTransforms ++ extraCheckTransforms).map(RunFirrtlTransformAnnotation(_))
- (new firrtl.stage.FirrtlStage).run(annos)
+ (new firrtl.stage.FirrtlStage).execute(Array.empty, annos)
testDir
}
diff --git a/src/test/scala/firrtlTests/execution/VerilogExecution.scala b/src/test/scala/firrtlTests/execution/VerilogExecution.scala
index bf3d1461..89f27609 100644
--- a/src/test/scala/firrtlTests/execution/VerilogExecution.scala
+++ b/src/test/scala/firrtlTests/execution/VerilogExecution.scala
@@ -21,7 +21,8 @@ trait VerilogExecution extends TestExecution {
// Run FIRRTL, emit Verilog file
val cAnno = FirrtlCircuitAnnotation(c)
val tdAnno = TargetDirAnnotation(testDir.getAbsolutePath)
- (new FirrtlStage).run(AnnotationSeq(Seq(cAnno, tdAnno) ++ customAnnotations))
+
+ (new FirrtlStage).execute(Array.empty, AnnotationSeq(Seq(cAnno, tdAnno)) ++ customAnnotations)
// Copy harness resource to test directory
val harness = new File(testDir, s"top.cpp")
diff --git a/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala b/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala
index e71eaedf..0a3cce67 100644
--- a/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala
+++ b/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala
@@ -7,7 +7,16 @@ import java.io.File
import firrtl.AnnotationSeq
import firrtl.annotations.{DeletedAnnotation, NoTargetAnnotation}
-import firrtl.options.{InputAnnotationFileAnnotation, OutputAnnotationFileAnnotation, Phase, WriteDeletedAnnotation}
+import firrtl.options.{
+ CustomFileEmission,
+ InputAnnotationFileAnnotation,
+ OutputAnnotationFileAnnotation,
+ Phase,
+ PhaseException,
+ StageOptions,
+ TargetDirAnnotation,
+ WriteDeletedAnnotation}
+import firrtl.options.Viewer.view
import firrtl.options.phases.{GetIncludes, WriteOutputAnnotations}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
@@ -100,9 +109,59 @@ class WriteOutputAnnotationsSpec extends AnyFlatSpec with Matchers with firrtl.t
out.toSeq should be (annotations)
}
+ it should "write CustomFileEmission annotations" in new Fixture {
+ val file = new File("write-CustomFileEmission-annotations.anno.json")
+ val annotations = Seq( TargetDirAnnotation(dir),
+ OutputAnnotationFileAnnotation(file.toString),
+ WriteOutputAnnotationsSpec.Custom("hello!") )
+ val serializedFileName = view[StageOptions](annotations).getBuildFileName("Custom", Some(".Emission"))
+ val expected = annotations.map {
+ case _: WriteOutputAnnotationsSpec.Custom => WriteOutputAnnotationsSpec.Replacement(serializedFileName)
+ case a => a
+ }
+
+ val out = phase.transform(annotations)
+
+ info("annotations are unmodified")
+ out.toSeq should be (annotations)
+
+ fileContainsAnnotations(new File(dir, file.toString), expected)
+
+ info(s"file '$serializedFileName' exists")
+ new File(serializedFileName) should (exist)
+ }
+
+ it should "error if multiple annotations try to write to the same file" in new Fixture {
+ val file = new File("write-CustomFileEmission-annotations-error.anno.json")
+ val annotations = Seq( TargetDirAnnotation(dir),
+ OutputAnnotationFileAnnotation(file.toString),
+ WriteOutputAnnotationsSpec.Custom("foo"),
+ WriteOutputAnnotationsSpec.Custom("bar") )
+ intercept[PhaseException] {
+ phase.transform(annotations)
+ }.getMessage should startWith ("Multiple CustomFileEmission annotations")
+ }
+
}
private object WriteOutputAnnotationsSpec {
+
case object FooAnnotation extends NoTargetAnnotation
+
case class BarAnnotation(x: Int) extends NoTargetAnnotation
+
+ case class Custom(value: String) extends NoTargetAnnotation with CustomFileEmission {
+
+ override protected def baseFileName(a: AnnotationSeq): String = "Custom"
+
+ override protected def suffix: Option[String] = Some(".Emission")
+
+ override def getBytes: Iterable[Byte] = value.getBytes
+
+ override def replacements(file: File): AnnotationSeq = Seq(Replacement(file.toString))
+
+ }
+
+ case class Replacement(file: String) extends NoTargetAnnotation
+
}
diff --git a/src/test/scala/firrtlTests/transforms/LegalizeReductions.scala b/src/test/scala/firrtlTests/transforms/LegalizeReductions.scala
index 664701c3..5368c54c 100644
--- a/src/test/scala/firrtlTests/transforms/LegalizeReductions.scala
+++ b/src/test/scala/firrtlTests/transforms/LegalizeReductions.scala
@@ -65,7 +65,7 @@ circuit $name :
TargetDirAnnotation(testDir.toString) ::
CompilerAnnotation(new MinimumVerilogCompiler) ::
Nil
- val resultAnnos = (new FirrtlStage).run(annos)
+ val resultAnnos = (new FirrtlStage).transform(annos)
val outputFilename = resultAnnos.collectFirst { case OutputFileAnnotation(f) => f }
outputFilename.toRight(s"Output file not found!")
// Copy Verilator harness