aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/firrtl/options
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/firrtl/options')
-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
3 files changed, 99 insertions, 12 deletions
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 {