diff options
| author | Chick Markley | 2019-06-03 16:22:43 -0700 |
|---|---|---|
| committer | GitHub | 2019-06-03 16:22:43 -0700 |
| commit | 821fe17b110e2a9017a335516227cb491c18cf43 (patch) | |
| tree | 22fe46c8f675ca3739f893890efc2479f367d9a1 /src | |
| parent | 820e2688f0cb966d4528ff074fdbdc623ed8f940 (diff) | |
| parent | 23641521174ba828feb32dc1c65c13a3d6b46970 (diff) | |
Merge pull request #1004 from freechipsproject/chisel-stage
Chisel stage
Diffstat (limited to 'src')
25 files changed, 1110 insertions, 56 deletions
diff --git a/src/main/scala/chisel3/ChiselExecutionOptions.scala b/src/main/scala/chisel3/ChiselExecutionOptions.scala index a3644829..eab49a3e 100644 --- a/src/main/scala/chisel3/ChiselExecutionOptions.scala +++ b/src/main/scala/chisel3/ChiselExecutionOptions.scala @@ -2,7 +2,9 @@ package chisel3 -import firrtl.{ExecutionOptionsManager, ComposableOptions} +import chisel3.stage.{NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation} + +import firrtl.{AnnotationSeq, ExecutionOptionsManager, ComposableOptions} //TODO: provide support for running firrtl as separate process, could alternatively be controlled by external driver //TODO: provide option for not saving chirrtl file, instead calling firrtl with in memory chirrtl @@ -16,7 +18,13 @@ case class ChiselExecutionOptions( runFirrtlCompiler: Boolean = true, printFullStackTrace: Boolean = false // var runFirrtlAsProcess: Boolean = false - ) extends ComposableOptions + ) extends ComposableOptions { + + def toAnnotations: AnnotationSeq = + (if (!runFirrtlCompiler) { Seq(NoRunFirrtlCompilerAnnotation) } else { Seq() }) ++ + (if (printFullStackTrace) { Some(PrintFullStackTraceAnnotation) } else { None }) + +} trait HasChiselExecutionOptions { self: ExecutionOptionsManager => diff --git a/src/main/scala/chisel3/Driver.scala b/src/main/scala/chisel3/Driver.scala index 303f4599..906ae7fc 100644 --- a/src/main/scala/chisel3/Driver.scala +++ b/src/main/scala/chisel3/Driver.scala @@ -5,11 +5,15 @@ package chisel3 import chisel3.internal.ErrorLog import chisel3.internal.firrtl._ import chisel3.experimental.{RawModule, RunFirrtlTransform} +import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, ChiselStage, ChiselExecutionResultView} +import chisel3.stage.phases.DriverCompatibility import java.io._ import firrtl._ import firrtl.annotations.JsonProtocol +import firrtl.options.Phase +import firrtl.options.Viewer.view import firrtl.util.{BackendCompilationUtilities => FirrtlBackendCompilationUtilities} /** @@ -196,8 +200,26 @@ object Driver extends BackendCompilationUtilities { def execute( // scalastyle:ignore method.length optionsManager: ExecutionOptionsManager with HasChiselExecutionOptions with HasFirrtlOptions, dut: () => RawModule): ChiselExecutionResult = { - val circuitOpt = try { - Some(elaborate(dut)) + + val annos = ChiselGeneratorAnnotation(dut) +: + (optionsManager.chiselOptions.toAnnotations ++ + optionsManager.firrtlOptions.toAnnotations ++ + optionsManager.commonOptions.toAnnotations) + + val phases: Seq[Phase] = + Seq( new DriverCompatibility.AddImplicitOutputFile, + new DriverCompatibility.AddImplicitOutputAnnotationFile, + new DriverCompatibility.DisableFirrtlStage, + new ChiselStage, + new DriverCompatibility.MutateOptionsManager(optionsManager), + new DriverCompatibility.ReEnableFirrtlStage, + new firrtl.stage.phases.DriverCompatibility.AddImplicitOutputFile, + new firrtl.stage.phases.DriverCompatibility.AddImplicitEmitter, + new chisel3.stage.phases.MaybeFirrtlStage ) + .map(firrtl.options.phases.DeletedWrapper(_)) + + val annosx = try { + phases.foldLeft(annos)( (a, p) => p.transform(a) ) } catch { case ce: ChiselException => val stackTrace = if (!optionsManager.chiselOptions.printFullStackTrace) { @@ -208,60 +230,10 @@ object Driver extends BackendCompilationUtilities { sw.toString } Predef.augmentString(stackTrace).lines.foreach(line => println(s"${ErrorLog.errTag} $line")) // scalastyle:ignore regex line.size.limit - None + annos } - circuitOpt.map { circuit => - // this little hack let's us set the topName with the circuit name if it has not been set from args - optionsManager.setTopNameIfNotSet(circuit.name) - - val firrtlOptions = optionsManager.firrtlOptions - val chiselOptions = optionsManager.chiselOptions - - val firrtlCircuit = Converter.convert(circuit) - - // Still emit to leave an artifact (and because this always has been the behavior) - val firrtlString = Driver.emit(circuit) - val firrtlFileName = firrtlOptions.getInputFileName(optionsManager) - val firrtlFile = new File(firrtlFileName) - - val w = new FileWriter(firrtlFile) - w.write(firrtlString) - w.close() - - // Emit the annotations because it has always been the behavior - val annotationFile = new File(optionsManager.getBuildFileName("anno.json")) - val af = new FileWriter(annotationFile) - val firrtlAnnos = circuit.annotations.map(_.toFirrtl) - af.write(JsonProtocol.serialize(firrtlAnnos)) - af.close() - - /** Find the set of transform classes associated with annotations then - * instantiate an instance of each transform - * @note Annotations targeting firrtl.Transform will not result in any - * transform being instantiated - */ - val transforms = circuit.annotations - .collect { case anno: RunFirrtlTransform => anno.transformClass } - .distinct - .filterNot(_ == classOf[firrtl.Transform]) - .map { transformClass: Class[_ <: Transform] => - transformClass.newInstance() - } - /* This passes the firrtl source and annotations directly to firrtl */ - optionsManager.firrtlOptions = optionsManager.firrtlOptions.copy( - firrtlCircuit = Some(firrtlCircuit), - annotations = optionsManager.firrtlOptions.annotations ++ firrtlAnnos, - customTransforms = optionsManager.firrtlOptions.customTransforms ++ transforms.toList) - - val firrtlExecutionResult = if(chiselOptions.runFirrtlCompiler) { - Some(firrtl.Driver.execute(optionsManager)) - } - else { - None - } - ChiselExecutionSuccess(Some(circuit), firrtlString, firrtlExecutionResult) - }.getOrElse(ChiselExecutionFailure("could not elaborate circuit")) + view[ChiselExecutionResult](annosx) } /** diff --git a/src/main/scala/chisel3/stage/ChiselAnnotations.scala b/src/main/scala/chisel3/stage/ChiselAnnotations.scala new file mode 100644 index 00000000..fb02173b --- /dev/null +++ b/src/main/scala/chisel3/stage/ChiselAnnotations.scala @@ -0,0 +1,105 @@ +// See LICENSE for license details. + +package chisel3.stage + +import firrtl.annotations.{Annotation, NoTargetAnnotation} +import firrtl.options.{HasShellOptions, OptionsException, ShellOption, Unserializable} + +import chisel3.{ChiselException, Module} +import chisel3.experimental.RawModule +import chisel3.internal.Builder +import chisel3.internal.firrtl.Circuit + +/** Mixin that indicates that this is an [[firrtl.annotations.Annotation]] used to generate a [[ChiselOptions]] view. + */ +sealed trait ChiselOption extends Unserializable { this: Annotation => } + +/** Disable the execution of the FIRRTL compiler by Chisel + */ +case object NoRunFirrtlCompilerAnnotation extends NoTargetAnnotation with ChiselOption with HasShellOptions { + + val options = Seq( + new ShellOption[Unit]( + longOption = "no-run-firrtl", + toAnnotationSeq = _ => Seq(NoRunFirrtlCompilerAnnotation), + helpText = "Do not run the FIRRTL compiler (generate FIRRTL IR from Chisel and exit)", + shortOption = Some("chnrf") ) ) + +} + +/** On an exception, this will cause the full stack trace to be printed as opposed to a pruned stack trace. + */ +case object PrintFullStackTraceAnnotation extends NoTargetAnnotation with ChiselOption with HasShellOptions { + + val options = Seq( + new ShellOption[Unit]( + longOption = "full-stacktrace", + toAnnotationSeq = _ => Seq(PrintFullStackTraceAnnotation), + helpText = "Show full stack trace when an exception is thrown" ) ) + +} + +/** An [[firrtl.annotations.Annotation]] storing a function that returns a Chisel module + * @param gen a generator function + */ +case class ChiselGeneratorAnnotation(gen: () => RawModule) extends NoTargetAnnotation with Unserializable { + + /** Run elaboration on the Chisel module generator function stored by this [[firrtl.annotations.Annotation]] + */ + def elaborate: ChiselCircuitAnnotation = try { + ChiselCircuitAnnotation(Builder.build(Module(gen()))) + } catch { + case e @ (_: OptionsException | _: ChiselException) => throw e + case e: Throwable => + throw new OptionsException(s"Exception thrown when elaborating ChiselGeneratorAnnotation", e) + } + +} + +object ChiselGeneratorAnnotation extends HasShellOptions { + + /** Construct a [[ChiselGeneratorAnnotation]] with a generator function that will try to construct a Chisel Module + * from using that Module's name. The Module must both exist in the class path and not take parameters. + * @param name a module name + * @throws firrtl.options.OptionsException if the module name is not found or if no parameterless constructor for + * that Module is found + */ + def apply(name: String): ChiselGeneratorAnnotation = { + val gen = () => try { + Class.forName(name).asInstanceOf[Class[_ <: RawModule]].newInstance() + } catch { + case e: ClassNotFoundException => + throw new OptionsException(s"Unable to locate module '$name'! (Did you misspell it?)", e) + case e: InstantiationException => + throw new OptionsException( + s"Unable to create instance of module '$name'! (Does this class take parameters?)", e) + } + ChiselGeneratorAnnotation(gen) + } + + val options = Seq( + new ShellOption[String]( + longOption = "module", + toAnnotationSeq = (a: String) => Seq(ChiselGeneratorAnnotation(a)), + helpText = "The name of a Chisel module to elaborate (module must be in the classpath)", + helpValueName = Some("<package>.<module>") ) ) + +} + +/** Stores a Chisel Circuit + * @param circuit a Chisel Circuit + */ +case class ChiselCircuitAnnotation(circuit: Circuit) extends NoTargetAnnotation with ChiselOption + +case class ChiselOutputFileAnnotation(file: String) extends NoTargetAnnotation with ChiselOption + +object ChiselOutputFileAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "chisel-output-file", + toAnnotationSeq = (a: String) => Seq(ChiselOutputFileAnnotation(a)), + helpText = "Write Chisel-generated FIRRTL to this file (default: <circuit-main>.fir)", + helpValueName = Some("<file>") ) ) + +} diff --git a/src/main/scala/chisel3/stage/ChiselCli.scala b/src/main/scala/chisel3/stage/ChiselCli.scala new file mode 100644 index 00000000..4f7ac19e --- /dev/null +++ b/src/main/scala/chisel3/stage/ChiselCli.scala @@ -0,0 +1,12 @@ +// See LICENSE for license details. + +package chisel3.stage + +import firrtl.options.Shell + +trait ChiselCli { this: Shell => + parser.note("Chisel Front End Options") + Seq( NoRunFirrtlCompilerAnnotation, + PrintFullStackTraceAnnotation ) + .foreach(_.addOptions(parser)) +} diff --git a/src/main/scala/chisel3/stage/ChiselOptions.scala b/src/main/scala/chisel3/stage/ChiselOptions.scala new file mode 100644 index 00000000..f7b9ccdf --- /dev/null +++ b/src/main/scala/chisel3/stage/ChiselOptions.scala @@ -0,0 +1,27 @@ +// See LICENSE for license details. + +package chisel3.stage + +import chisel3.internal.firrtl.Circuit + +class ChiselOptions private[stage] ( + val runFirrtlCompiler: Boolean = true, + val printFullStackTrace: Boolean = false, + val outputFile: Option[String] = None, + val chiselCircuit: Option[Circuit] = None) { + + private[stage] def copy( + runFirrtlCompiler: Boolean = runFirrtlCompiler, + printFullStackTrace: Boolean = printFullStackTrace, + outputFile: Option[String] = outputFile, + chiselCircuit: Option[Circuit] = chiselCircuit ): ChiselOptions = { + + new ChiselOptions( + runFirrtlCompiler = runFirrtlCompiler, + printFullStackTrace = printFullStackTrace, + outputFile = outputFile, + chiselCircuit = chiselCircuit ) + + } + +} diff --git a/src/main/scala/chisel3/stage/ChiselStage.scala b/src/main/scala/chisel3/stage/ChiselStage.scala new file mode 100644 index 00000000..1e92aaf6 --- /dev/null +++ b/src/main/scala/chisel3/stage/ChiselStage.scala @@ -0,0 +1,26 @@ +// See LICENSE for license details. + +package chisel3.stage + +import firrtl.AnnotationSeq +import firrtl.options.{Phase, Shell, Stage} +import firrtl.stage.FirrtlCli + +class ChiselStage extends Stage { + val shell: Shell = new Shell("chisel") with ChiselCli with FirrtlCli + + private val phases: Seq[Phase] = + Seq( new chisel3.stage.phases.Checks, + new chisel3.stage.phases.Elaborate, + new chisel3.stage.phases.AddImplicitOutputFile, + new chisel3.stage.phases.AddImplicitOutputAnnotationFile, + new chisel3.stage.phases.Emitter, + new chisel3.stage.phases.Convert, + new chisel3.stage.phases.MaybeFirrtlStage ) + .map(firrtl.options.phases.DeletedWrapper(_)) + + def run(annotations: AnnotationSeq): AnnotationSeq = + /* @todo: Should this be wrapped in a try/catch? */ + phases.foldLeft(annotations)( (a, f) => f.transform(a) ) + +} diff --git a/src/main/scala/chisel3/stage/package.scala b/src/main/scala/chisel3/stage/package.scala new file mode 100644 index 00000000..67d38ae7 --- /dev/null +++ b/src/main/scala/chisel3/stage/package.scala @@ -0,0 +1,58 @@ +// See LICENSE for license details. + +package chisel3 + +import firrtl._ +import firrtl.annotations.DeletedAnnotation +import firrtl.options.OptionsView + +import chisel3.internal.firrtl.{Circuit => ChiselCircuit} +import chisel3.stage.phases.{Convert, Emitter} + +package object stage { + + implicit object ChiselOptionsView extends OptionsView[ChiselOptions] { + + def view(options: AnnotationSeq): ChiselOptions = options + .collect { case a: ChiselOption => a } + .foldLeft(new ChiselOptions()){ (c, x) => + x match { + case _: NoRunFirrtlCompilerAnnotation.type => c.copy(runFirrtlCompiler = false) + case _: PrintFullStackTraceAnnotation.type => c.copy(printFullStackTrace = true) + case ChiselOutputFileAnnotation(f) => c.copy(outputFile = Some(f)) + case ChiselCircuitAnnotation(a) => c.copy(chiselCircuit = Some(a)) + } + } + + } + + private[chisel3] implicit object ChiselExecutionResultView extends OptionsView[ChiselExecutionResult] { + + lazy val dummyWriteEmitted = new firrtl.stage.phases.WriteEmitted + lazy val dummyConvert = new Convert + lazy val dummyEmitter = new Emitter + + def view(options: AnnotationSeq): ChiselExecutionResult = { + var chiselCircuit: Option[ChiselCircuit] = None + var chirrtlCircuit: Option[String] = None + + options.foreach { + case DeletedAnnotation(dummyConvert.name, ChiselCircuitAnnotation(a)) => chiselCircuit = Some(a) + case DeletedAnnotation(dummyEmitter.name, EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit(_, a, _))) => + chirrtlCircuit = Some(a) + case _ => + } + + val fResult = firrtl.stage.phases.DriverCompatibility.firrtlResultView(options) + + (chiselCircuit, chirrtlCircuit) match { + case (None, _) => ChiselExecutionFailure("Failed to elaborate Chisel circuit") + case (Some(_), None) => ChiselExecutionFailure("Failed to convert Chisel circuit to FIRRTL") + case (Some(a), Some(b)) => ChiselExecutionSuccess( Some(a), b, Some(fResult)) + } + + } + + } + +} diff --git a/src/main/scala/chisel3/stage/phases/AddImplicitOutputAnnotationFile.scala b/src/main/scala/chisel3/stage/phases/AddImplicitOutputAnnotationFile.scala new file mode 100644 index 00000000..de251ab6 --- /dev/null +++ b/src/main/scala/chisel3/stage/phases/AddImplicitOutputAnnotationFile.scala @@ -0,0 +1,25 @@ +// See LICENSE for license details. + +package chisel3.stage.phases + +import chisel3.stage.ChiselCircuitAnnotation +import firrtl.AnnotationSeq +import firrtl.options.{OutputAnnotationFileAnnotation, Phase} + +/** Adds an [[firrtl.options.OutputAnnotationFileAnnotation]] if one does not exist. This replicates old behavior where + * an output annotation file was always written. + */ +class AddImplicitOutputAnnotationFile extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = annotations + .collectFirst{ case _: OutputAnnotationFileAnnotation => annotations } + .getOrElse{ + + val x: Option[AnnotationSeq] = annotations + .collectFirst{ case a: ChiselCircuitAnnotation => + OutputAnnotationFileAnnotation(a.circuit.name) +: annotations } + + x.getOrElse(annotations) + } + +} diff --git a/src/main/scala/chisel3/stage/phases/AddImplicitOutputFile.scala b/src/main/scala/chisel3/stage/phases/AddImplicitOutputFile.scala new file mode 100644 index 00000000..4a4dac72 --- /dev/null +++ b/src/main/scala/chisel3/stage/phases/AddImplicitOutputFile.scala @@ -0,0 +1,25 @@ +// See LICENSE for license details. + +package chisel3.stage.phases + +import firrtl.AnnotationSeq +import firrtl.options.Phase + +import chisel3.stage.{ChiselCircuitAnnotation, ChiselOutputFileAnnotation} + +/** Add a output file for a Chisel circuit, derived from the top module in the circuit, if no + * [[ChiselOutputFileAnnotation]] already exists. + */ +class AddImplicitOutputFile extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = + annotations.collectFirst{ case _: ChiselOutputFileAnnotation => annotations }.getOrElse{ + + val x: Option[AnnotationSeq] = annotations + .collectFirst{ case a: ChiselCircuitAnnotation => + ChiselOutputFileAnnotation(a.circuit.name) +: annotations } + + x.getOrElse(annotations) + } + +} diff --git a/src/main/scala/chisel3/stage/phases/Checks.scala b/src/main/scala/chisel3/stage/phases/Checks.scala new file mode 100644 index 00000000..e2606019 --- /dev/null +++ b/src/main/scala/chisel3/stage/phases/Checks.scala @@ -0,0 +1,49 @@ +// See LICENSE for license details. + +package chisel3.stage.phases + +import chisel3.stage.{ChiselOutputFileAnnotation, NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation} + +import firrtl.AnnotationSeq +import firrtl.annotations.Annotation +import firrtl.options.{OptionsException, Phase} + +/** Sanity checks an [[firrtl.AnnotationSeq]] before running the main [[firrtl.options.Phase]]s of + * [[chisel3.stage.ChiselStage]]. + */ +class Checks extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = { + val noF, st, outF = collection.mutable.ListBuffer[Annotation]() + annotations.foreach { + case a: NoRunFirrtlCompilerAnnotation.type => a +=: noF + case a: PrintFullStackTraceAnnotation.type => a +=: st + case a: ChiselOutputFileAnnotation => a +=: outF + case _ => + } + + if (noF.size > 1) { + throw new OptionsException( + s"""|At most one NoRunFirrtlCompilerAnnotation can be specified, but found '${noF.size}'. Did you duplicate: + | - option or annotation: -chnrf, --no-run-firrtl, NoRunFirrtlCompilerAnnotation + |""".stripMargin) + } + + if (st.size > 1) { + throw new OptionsException( + s"""|At most one PrintFullStackTraceAnnotation can be specified, but found '${noF.size}'. Did you duplicate: + | - option or annotation: --full-stacktrace, PrintFullStackTraceAnnotation + |""".stripMargin) + } + + if (outF.size > 1) { + throw new OptionsException( + s"""|At most one Chisel output file can be specified but found '${outF.size}'. Did you duplicate: + | - option or annotation: --chisel-output-file, ChiselOutputFileAnnotation + |""".stripMargin) + } + + annotations + } + +} diff --git a/src/main/scala/chisel3/stage/phases/Convert.scala b/src/main/scala/chisel3/stage/phases/Convert.scala new file mode 100644 index 00000000..174030ae --- /dev/null +++ b/src/main/scala/chisel3/stage/phases/Convert.scala @@ -0,0 +1,41 @@ +// See LICENSE for license details. + +package chisel3.stage.phases + +import chisel3.experimental.RunFirrtlTransform +import chisel3.internal.firrtl.Converter +import chisel3.stage.ChiselCircuitAnnotation + +import firrtl.{AnnotationSeq, Transform} +import firrtl.options.Phase +import firrtl.stage.{FirrtlCircuitAnnotation, RunFirrtlTransformAnnotation} + +/** This prepares a [[ChiselCircuitAnnotation]] for compilation with FIRRTL. This does three things: + * - Uses [[chisel3.internal.firrtl.Converter]] to generate a [[FirrtlCircuitAnnotation]] + * - Extracts all [[firrtl.annotations.Annotation]]s from the [[chisel3.internal.firrtl.Circuit]] + * - Generates any needed [[RunFirrtlTransformAnnotation]]s from extracted [[firrtl.annotations.Annotation]]s + */ +class Convert extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap { + case a: ChiselCircuitAnnotation => { + /* Convert this Chisel Circuit to a FIRRTL Circuit */ + Some(FirrtlCircuitAnnotation(Converter.convert(a.circuit))) ++ + /* Convert all Chisel Annotations to FIRRTL Annotations */ + a + .circuit + .annotations + .map(_.toFirrtl) ++ + /* Add requested FIRRTL Transforms for any Chisel Annotations which mixed in RunFirrtlTransform */ + a + .circuit + .annotations + .collect { case b: RunFirrtlTransform => b.transformClass } + .distinct + .filterNot(_ == classOf[firrtl.Transform]) + .map { c: Class[_ <: Transform] => RunFirrtlTransformAnnotation(c.newInstance()) } + } + case a => Some(a) + } + +} diff --git a/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala b/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala new file mode 100644 index 00000000..b7674aa1 --- /dev/null +++ b/src/main/scala/chisel3/stage/phases/DriverCompatibility.scala @@ -0,0 +1,115 @@ +// See LICENSE for license details. + +package chisel3.stage.phases + +import firrtl.{AnnotationSeq, ExecutionOptionsManager, HasFirrtlOptions} +import firrtl.annotations.NoTargetAnnotation +import firrtl.options.{OutputAnnotationFileAnnotation, Phase} +import firrtl.stage.{FirrtlCircuitAnnotation, RunFirrtlTransformAnnotation} +import firrtl.stage.phases.DriverCompatibility.TopNameAnnotation + +import chisel3.HasChiselExecutionOptions +import chisel3.stage.{NoRunFirrtlCompilerAnnotation, ChiselOutputFileAnnotation} + +/** This provides components of a compatibility wrapper around Chisel's deprecated [[chisel3.Driver]]. + * + * Primarily, this object includes [[firrtl.options.Phase Phase]]s that generate [[firrtl.annotations.Annotation]]s + * derived from the deprecated [[firrtl.stage.phases.DriverCompatibility.TopNameAnnotation]]. + */ +object DriverCompatibility { + + /** Adds a [[ChiselOutputFileAnnotation]] derived from a [[TopNameAnnotation]] if no [[ChiselOutputFileAnnotation]] + * already exists. If no [[TopNameAnnotation]] exists, then no [[firrtl.stage.OutputFileAnnotation]] is added. ''This is not a + * replacement for [[chisel3.stage.phases.AddImplicitOutputFile AddImplicitOutputFile]] as this only adds an output + * file based on a discovered top name and not on a discovered elaborated circuit.'' Consequently, this will provide + * the correct behavior before a circuit has been elaborated. + * @note the output suffix is unspecified and will be set by the underlying [[firrtl.EmittedComponent]] + */ + private[chisel3] class AddImplicitOutputFile extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = { + val hasOutputFile = annotations + .collectFirst{ case a: ChiselOutputFileAnnotation => a } + .isDefined + lazy val top = annotations.collectFirst{ case TopNameAnnotation(a) => a } + + if (!hasOutputFile && top.isDefined) { + ChiselOutputFileAnnotation(top.get) +: annotations + } else { + annotations + } + } + } + + /** If a [[firrtl.options.OutputAnnotationFileAnnotation]] does not exist, this adds one derived from a + * [[TopNameAnnotation]]. ''This is not a replacement for [[chisel3.stage.phases.AddImplicitOutputAnnotationFile]] as + * this only adds an output annotation file based on a discovered top name.'' Consequently, this will provide the + * correct behavior before a circuit has been elaborated. + * @note the output suffix is unspecified and will be set by [[firrtl.options.phases.WriteOutputAnnotations]] + */ + private[chisel3] class AddImplicitOutputAnnotationFile extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = + annotations + .collectFirst{ case _: OutputAnnotationFileAnnotation => annotations } + .getOrElse{ + val top = annotations.collectFirst{ case TopNameAnnotation(a) => a } + if (top.isDefined) { + OutputAnnotationFileAnnotation(top.get) +: annotations + } else { + annotations + } + } + } + + private[chisel3] case object RunFirrtlCompilerAnnotation extends NoTargetAnnotation + + /** Disables the execution of [[firrtl.stage.FirrtlStage]]. This can be used to call [[chisel3.stage.ChiselStage]] and + * guarantee that the FIRRTL compiler will not run. This is necessary for certain [[chisel3.Driver]] compatibility + * situations where you need to do something between Chisel compilation and FIRRTL compilations, e.g., update a + * mutable data structure. + */ + private[chisel3] class DisableFirrtlStage extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = annotations + .collectFirst { case NoRunFirrtlCompilerAnnotation => annotations } + .getOrElse { Seq(RunFirrtlCompilerAnnotation, NoRunFirrtlCompilerAnnotation) ++ annotations } + } + + private[chisel3] class ReEnableFirrtlStage extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = annotations + .collectFirst { case RunFirrtlCompilerAnnotation => + val a: AnnotationSeq = annotations.filter { + case NoRunFirrtlCompilerAnnotation | RunFirrtlCompilerAnnotation => false + case _ => true + } + a + } + .getOrElse{ annotations } + + } + + /** Mutate an input [[firrtl.ExecutionOptionsManager]] based on information encoded in an [[firrtl.AnnotationSeq]]. + * This is intended to be run between [[chisel3.stage.ChiselStage ChiselStage]] and [[firrtl.stage.FirrtlStage]] if + * you want to have backwards compatibility with an [[firrtl.ExecutionOptionsManager]]. + */ + private[chisel3] class MutateOptionsManager( + optionsManager: ExecutionOptionsManager with HasChiselExecutionOptions with HasFirrtlOptions) extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = { + + val firrtlCircuit = annotations.collectFirst{ case FirrtlCircuitAnnotation(a) => a } + optionsManager.firrtlOptions = optionsManager.firrtlOptions.copy( + firrtlCircuit = firrtlCircuit, + annotations = optionsManager.firrtlOptions.annotations ++ annotations, + customTransforms = optionsManager.firrtlOptions.customTransforms ++ + annotations.collect{ case RunFirrtlTransformAnnotation(a) => a } ) + + annotations + + } + + } + +} diff --git a/src/main/scala/chisel3/stage/phases/Elaborate.scala b/src/main/scala/chisel3/stage/phases/Elaborate.scala new file mode 100644 index 00000000..0b0d71fb --- /dev/null +++ b/src/main/scala/chisel3/stage/phases/Elaborate.scala @@ -0,0 +1,42 @@ +// See LICENSE for license details. + +package chisel3.stage.phases + +import java.io.{PrintWriter, StringWriter} + +import chisel3.ChiselException +import chisel3.internal.ErrorLog +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselOptions} +import firrtl.AnnotationSeq +import firrtl.options.Viewer.view +import firrtl.options.{OptionsException, Phase} + +/** Elaborate all [[chisel3.stage.ChiselGeneratorAnnotation]]s into [[chisel3.stage.ChiselCircuitAnnotation]]s. + */ +class Elaborate extends Phase { + + /** + * @todo Change this to print to STDERR (`Console.err.println`) + */ + def transform(annotations: AnnotationSeq): AnnotationSeq = annotations.flatMap { + case a: ChiselGeneratorAnnotation => + try { + Some(a.elaborate) + } catch { + case e: OptionsException => throw e + case e: ChiselException => + val copts = view[ChiselOptions](annotations) + val stackTrace = if (!copts.printFullStackTrace) { + e.chiselStackTrace + } else { + val s = new StringWriter + e.printStackTrace(new PrintWriter(s)) + s.toString + } + Predef.augmentString(stackTrace).lines.foreach(line => println(s"${ErrorLog.errTag} $line")) + Some(a) + } + case a => Some(a) + } + +} diff --git a/src/main/scala/chisel3/stage/phases/Emitter.scala b/src/main/scala/chisel3/stage/phases/Emitter.scala new file mode 100644 index 00000000..1bdb9f8d --- /dev/null +++ b/src/main/scala/chisel3/stage/phases/Emitter.scala @@ -0,0 +1,44 @@ +// See LICENSE for license details. + +package chisel3.stage.phases + +import firrtl.{AnnotationSeq, EmittedFirrtlCircuit, EmittedFirrtlCircuitAnnotation} +import firrtl.annotations.DeletedAnnotation +import firrtl.options.{Phase, StageOptions} +import firrtl.options.Viewer.view + +import chisel3.internal.firrtl.{Emitter => OldEmitter} +import chisel3.stage.{ChiselCircuitAnnotation, ChiselOptions} + +import java.io.{File, FileWriter} + +/** Emit a [[chisel3.stage.ChiselCircuitAnnotation]] to a file if a [[chisel3.stage.ChiselOutputFileAnnotation]] is + * present. A deleted [[firrtl.EmittedFirrtlCircuitAnnotation]] is added. + * + * @todo This should be switched to support correct emission of multiple circuits to multiple files. The API should + * likely mirror how the [[firrtl.stage.phases.Compiler]] parses annotations into "global" annotations and + * left-associative per-circuit annotations. + * @todo The use of the deleted [[firrtl.EmittedFirrtlCircuitAnnotation]] is a kludge to provide some breadcrumbs such + * that the emitted CHIRRTL can be provided back to the old Driver. This should be removed or a better solution + * developed. + */ +class Emitter extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = { + val copts = view[ChiselOptions](annotations) + val sopts = view[StageOptions](annotations) + + annotations.flatMap { + case a: ChiselCircuitAnnotation if copts.outputFile.isDefined => + val file = new File(sopts.getBuildFileName(copts.outputFile.get, Some(".fir"))) + val emitted = OldEmitter.emit(a.circuit) + val w = new FileWriter(file) + w.write(emitted) + w.close() + val anno = EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit(a.circuit.name, emitted, ".fir")) + Seq(DeletedAnnotation(name, anno), a) + case a => Some(a) + } + } + +} diff --git a/src/main/scala/chisel3/stage/phases/MaybeFirrtlStage.scala b/src/main/scala/chisel3/stage/phases/MaybeFirrtlStage.scala new file mode 100644 index 00000000..f830c182 --- /dev/null +++ b/src/main/scala/chisel3/stage/phases/MaybeFirrtlStage.scala @@ -0,0 +1,19 @@ +// See LICENSE for license details. + +package chisel3.stage.phases + +import chisel3.stage.NoRunFirrtlCompilerAnnotation + +import firrtl.AnnotationSeq +import firrtl.options.Phase +import firrtl.stage.FirrtlStage + +/** Run [[firrtl.stage.FirrtlStage]] if a [[chisel3.stage.NoRunFirrtlCompilerAnnotation]] is not present. + */ +class MaybeFirrtlStage extends Phase { + + def transform(annotations: AnnotationSeq): AnnotationSeq = annotations + .collectFirst { case NoRunFirrtlCompilerAnnotation => annotations } + .getOrElse { (new FirrtlStage).transform(annotations) } + +} diff --git a/src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala b/src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala new file mode 100644 index 00000000..c478db27 --- /dev/null +++ b/src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala @@ -0,0 +1,70 @@ +// See LICENSE for license details. + +package chisel3.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import chisel3.stage.{NoRunFirrtlCompilerAnnotation, ChiselOutputFileAnnotation} + +import firrtl.options.{OutputAnnotationFileAnnotation, StageOptions} +import firrtl.options.Viewer.view +import firrtl.stage.phases.DriverCompatibility.TopNameAnnotation + +class DriverCompatibilitySpec extends FlatSpec with Matchers { + + behavior of classOf[DriverCompatibility.AddImplicitOutputFile].toString + + it should "do nothing if a ChiselOutputFileAnnotation is present" in { + val annotations = Seq( + ChiselOutputFileAnnotation("Foo"), + TopNameAnnotation("Bar") ) + (new DriverCompatibility.AddImplicitOutputFile).transform(annotations).toSeq should be (annotations) + } + + it should "add a ChiselOutputFileAnnotation derived from a TopNameAnnotation" in { + val annotations = Seq( TopNameAnnotation("Bar") ) + val expected = ChiselOutputFileAnnotation("Bar") +: annotations + (new DriverCompatibility.AddImplicitOutputFile).transform(annotations).toSeq should be (expected) + } + + behavior of classOf[DriverCompatibility.AddImplicitOutputAnnotationFile].toString + + it should "do nothing if an OutputAnnotationFileAnnotation is present" in { + val annotations = Seq( + OutputAnnotationFileAnnotation("Foo"), + TopNameAnnotation("Bar") ) + (new DriverCompatibility.AddImplicitOutputAnnotationFile).transform(annotations).toSeq should be (annotations) + } + + it should "add an OutputAnnotationFileAnnotation derived from a TopNameAnnotation" in { + val annotations = Seq( TopNameAnnotation("Bar") ) + val expected = OutputAnnotationFileAnnotation("Bar") +: annotations + (new DriverCompatibility.AddImplicitOutputAnnotationFile).transform(annotations).toSeq should be (expected) + } + + behavior of classOf[DriverCompatibility.DisableFirrtlStage].toString + + it should "add a NoRunFirrtlCompilerAnnotation if one does not exist" in { + val annos = Seq(NoRunFirrtlCompilerAnnotation) + val expected = DriverCompatibility.RunFirrtlCompilerAnnotation +: annos + (new DriverCompatibility.DisableFirrtlStage).transform(Seq.empty).toSeq should be (expected) + } + + it should "NOT add a NoRunFirrtlCompilerAnnotation if one already exists" in { + val annos = Seq(NoRunFirrtlCompilerAnnotation) + (new DriverCompatibility.DisableFirrtlStage).transform(annos).toSeq should be (annos) + } + + behavior of classOf[DriverCompatibility.ReEnableFirrtlStage].toString + + it should "NOT strip a NoRunFirrtlCompilerAnnotation if NO RunFirrtlCompilerAnnotation is present" in { + val annos = Seq(NoRunFirrtlCompilerAnnotation, DriverCompatibility.RunFirrtlCompilerAnnotation) + (new DriverCompatibility.ReEnableFirrtlStage).transform(annos).toSeq should be (Seq.empty) + } + + it should "strip a NoRunFirrtlCompilerAnnotation if a RunFirrtlCompilerAnnotation is present" in { + val annos = Seq(NoRunFirrtlCompilerAnnotation) + (new DriverCompatibility.ReEnableFirrtlStage).transform(annos).toSeq should be (annos) + } + +} diff --git a/src/test/scala/chiselTests/DriverSpec.scala b/src/test/scala/chiselTests/DriverSpec.scala index 612bdef2..8fc58e21 100644 --- a/src/test/scala/chiselTests/DriverSpec.scala +++ b/src/test/scala/chiselTests/DriverSpec.scala @@ -28,6 +28,7 @@ class DriverSpec extends FreeSpec with Matchers { val exts = List("anno.json", "fir", "v") for (ext <- exts) { val dummyOutput = new File(targetDir, "DummyModule" + "." + ext) + info(s"${dummyOutput.toString} exists") dummyOutput.exists() should be(true) dummyOutput.delete() } @@ -44,6 +45,7 @@ class DriverSpec extends FreeSpec with Matchers { val exts = List("anno.json", "fir", "v") for (ext <- exts) { val dummyOutput = new File(targetDir, "dm" + "." + ext) + info(s"${dummyOutput.toString} exists") dummyOutput.exists() should be(true) dummyOutput.delete() } @@ -53,14 +55,21 @@ class DriverSpec extends FreeSpec with Matchers { } } + "execute returns a chisel execution result" in { val targetDir = "test_run_dir" val args = Array("--compiler", "low", "--target-dir", targetDir) + + info("Driver returned a ChiselExecutionSuccess") val result = Driver.execute(args, () => new DummyModule) result shouldBe a[ChiselExecutionSuccess] + + info("emitted circuit included 'circuit DummyModule'") val successResult = result.asInstanceOf[ChiselExecutionSuccess] successResult.emitted should include ("circuit DummyModule") + val dummyOutput = new File(targetDir, "DummyModule.lo.fir") + info(s"${dummyOutput.toString} exists") dummyOutput.exists() should be(true) dummyOutput.delete() } diff --git a/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala b/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala new file mode 100644 index 00000000..c89955f2 --- /dev/null +++ b/src/test/scala/chiselTests/stage/ChiselAnnotationsSpec.scala @@ -0,0 +1,65 @@ +// See LICENSE for license details. + +package chiselTests.stage + +import org.scalatest.{FlatSpec, Matchers} + +import chisel3._ +import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation} +import chisel3.experimental.RawModule + +import firrtl.options.OptionsException + +class ChiselAnnotationsSpecFoo extends RawModule { + val in = IO(Input(Bool())) + val out = IO(Output(Bool())) + out := ~in +} + +class ChiselAnnotationsSpecBaz(name: String) extends ChiselAnnotationsSpecFoo { + override val desiredName = name +} + +class ChiselAnnotationsSpecQux extends ChiselAnnotationsSpecFoo { + /* This printf requires an implicit clock and reset, but RawModule has none. This will thereby fail elaboration. */ + printf("hello") +} + +class ChiselAnnotation + +class ChiselAnnotationsSpec extends FlatSpec with Matchers { + + behavior of "ChiselGeneratorAnnotation elaboration" + + it should "elaborate to a ChiselCircuitAnnotation" in { + val annotation = ChiselGeneratorAnnotation(() => new ChiselAnnotationsSpecFoo) + annotation.elaborate shouldBe a [ChiselCircuitAnnotation] + } + + it should "throw an exception if elaboration fails" in { + val annotation = ChiselGeneratorAnnotation(() => new ChiselAnnotationsSpecQux) + intercept [ChiselException] { annotation.elaborate } + } + + behavior of "ChiselGeneratorAnnotation when stringly constructing from Module names" + + it should "elaborate from a String" in { + val annotation = ChiselGeneratorAnnotation("chiselTests.stage.ChiselAnnotationsSpecFoo") + annotation.elaborate shouldBe a [ChiselCircuitAnnotation] + } + + it should "throw an exception if elaboration from a String refers to nonexistant class" in { + val bar = "chiselTests.stage.ChiselAnnotationsSpecBar" + val annotation = ChiselGeneratorAnnotation(bar) + intercept [OptionsException] { annotation.elaborate } + .getMessage should startWith (s"Unable to locate module '$bar'") + } + + it should "throw an exception if elaboration from a String refers to an anonymous class" in { + val baz = "chiselTests.stage.ChiselAnnotationsSpecBaz" + val annotation = ChiselGeneratorAnnotation(baz) + intercept [OptionsException] { annotation.elaborate } + .getMessage should startWith (s"Unable to create instance of module '$baz'") + } + +} diff --git a/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala b/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala new file mode 100644 index 00000000..7dbeb9fa --- /dev/null +++ b/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala @@ -0,0 +1,40 @@ +// See LICENSE for license details. + +package chiselTests.stage + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.options.Viewer.view + +import chisel3.stage._ +import chisel3.internal.firrtl.Circuit + +class ChiselOptionsViewSpec extends FlatSpec with Matchers { + + behavior of ChiselOptionsView.getClass.getName + + it should "construct a view from an AnnotationSeq" in { + val bar = Circuit("bar", Seq.empty, Seq.empty) + val annotations = Seq( + NoRunFirrtlCompilerAnnotation, + PrintFullStackTraceAnnotation, + ChiselOutputFileAnnotation("foo"), + ChiselCircuitAnnotation(bar) + ) + val out = view[ChiselOptions](annotations) + + info("runFirrtlCompiler was set to false") + out.runFirrtlCompiler should be (false) + + info("printFullStackTrace was set to true") + out.printFullStackTrace should be (true) + + info("outputFile was set to 'foo'") + out.outputFile should be (Some("foo")) + + info("chiselCircuit was set to circuit 'bar'") + out.chiselCircuit should be (Some(bar)) + + } + +} diff --git a/src/test/scala/chiselTests/stage/phases/AddImplicitOutputAnnotationFileSpec.scala b/src/test/scala/chiselTests/stage/phases/AddImplicitOutputAnnotationFileSpec.scala new file mode 100644 index 00000000..f5fe0440 --- /dev/null +++ b/src/test/scala/chiselTests/stage/phases/AddImplicitOutputAnnotationFileSpec.scala @@ -0,0 +1,42 @@ +// See LICENSE for license details. + +package chiselTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import chisel3.experimental.RawModule +import chisel3.stage.ChiselGeneratorAnnotation +import chisel3.stage.phases.{AddImplicitOutputAnnotationFile, Elaborate} + +import firrtl.AnnotationSeq +import firrtl.options.{OutputAnnotationFileAnnotation, Phase} + +class AddImplicitOutputAnnotationFileSpec extends FlatSpec with Matchers { + + class Foo extends RawModule { override val desiredName = "Foo" } + + class Fixture { val phase: Phase = new AddImplicitOutputAnnotationFile } + + behavior of classOf[AddImplicitOutputAnnotationFile].toString + + it should "not override an existing OutputAnnotationFileAnnotation" in new Fixture { + val annotations: AnnotationSeq = Seq( + ChiselGeneratorAnnotation(() => new Foo), + OutputAnnotationFileAnnotation("Bar") ) + + Seq( new Elaborate, phase ) + .foldLeft(annotations)((a, p) => p.transform(a)) + .collect{ case a: OutputAnnotationFileAnnotation => a.file } + .toSeq should be (Seq("Bar")) + } + + it should "generate an OutputAnnotationFileAnnotation from a ChiselCircuitAnnotation" in new Fixture { + val annotations: AnnotationSeq = Seq( ChiselGeneratorAnnotation(() => new Foo) ) + + Seq( new Elaborate, phase ) + .foldLeft(annotations)((a, p) => p.transform(a)) + .collect{ case a: OutputAnnotationFileAnnotation => a.file } + .toSeq should be (Seq("Foo")) + } + +} diff --git a/src/test/scala/chiselTests/stage/phases/AddImplicitOutputFileSpec.scala b/src/test/scala/chiselTests/stage/phases/AddImplicitOutputFileSpec.scala new file mode 100644 index 00000000..411aa6ba --- /dev/null +++ b/src/test/scala/chiselTests/stage/phases/AddImplicitOutputFileSpec.scala @@ -0,0 +1,49 @@ +// See LICENSE for license details. + +package chiselTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import chisel3.experimental.RawModule +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselOutputFileAnnotation} +import chisel3.stage.phases.{AddImplicitOutputFile, Elaborate} + +import firrtl.AnnotationSeq +import firrtl.options.{Phase, StageOptions, TargetDirAnnotation} +import firrtl.options.Viewer.view + +class AddImplicitOutputFileSpec extends FlatSpec with Matchers { + + class Foo extends RawModule { override val desiredName = "Foo" } + + class Fixture { val phase: Phase = new AddImplicitOutputFile } + + behavior of classOf[AddImplicitOutputFile].toString + + it should "not override an existing ChiselOutputFileAnnotation" in new Fixture { + val annotations: AnnotationSeq = Seq( + ChiselGeneratorAnnotation(() => new Foo), + ChiselOutputFileAnnotation("Bar") ) + + Seq( new Elaborate, phase ) + .foldLeft(annotations)((a, p) => p.transform(a)) + .collect{ case a: ChiselOutputFileAnnotation => a.file } + .toSeq should be (Seq("Bar")) + } + + it should "generate a ChiselOutputFileAnnotation from a ChiselCircuitAnnotation" in new Fixture { + val annotations: AnnotationSeq = Seq( + ChiselGeneratorAnnotation(() => new Foo), + TargetDirAnnotation("test_run_dir") ) + + Seq( new Elaborate, phase ) + .foldLeft(annotations)((a, p) => p.transform(a)) + .collect{ case a: ChiselOutputFileAnnotation => a.file } + .toSeq should be (Seq("Foo")) + } + + it should "do nothing to an empty annotation sequence" in new Fixture { + phase.transform(AnnotationSeq(Seq.empty)).toSeq should be (empty) + } + +} diff --git a/src/test/scala/chiselTests/stage/phases/ChecksSpec.scala b/src/test/scala/chiselTests/stage/phases/ChecksSpec.scala new file mode 100644 index 00000000..6d01e38e --- /dev/null +++ b/src/test/scala/chiselTests/stage/phases/ChecksSpec.scala @@ -0,0 +1,43 @@ +// See LICENSE for license details. + +package chiselTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import chisel3.stage.{ChiselOutputFileAnnotation, NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation} +import chisel3.stage.phases.Checks + +import firrtl.AnnotationSeq +import firrtl.annotations.NoTargetAnnotation +import firrtl.options.{OptionsException, Phase} + +class ChecksSpec extends FlatSpec with Matchers { + + def checkExceptionMessage(phase: Phase, annotations: AnnotationSeq, messageStart: String): Unit = + intercept[OptionsException]{ phase.transform(annotations) }.getMessage should startWith(messageStart) + + class Fixture { val phase: Phase = new Checks } + + behavior of classOf[Checks].toString + + it should "do nothing on sane annotation sequences" in new Fixture { + val a = Seq(NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation) + phase.transform(a).toSeq should be (a) + } + + it should "throw an OptionsException if more than one NoRunFirrtlCompilerAnnotation is specified" in new Fixture { + val a = Seq(NoRunFirrtlCompilerAnnotation, NoRunFirrtlCompilerAnnotation) + checkExceptionMessage(phase, a, "At most one NoRunFirrtlCompilerAnnotation") + } + + it should "throw an OptionsException if more than one PrintFullStackTraceAnnotation is specified" in new Fixture { + val a = Seq(PrintFullStackTraceAnnotation, PrintFullStackTraceAnnotation) + checkExceptionMessage(phase, a, "At most one PrintFullStackTraceAnnotation") + } + + it should "throw an OptionsException if more than one ChiselOutputFileAnnotation is specified" in new Fixture { + val a = Seq(ChiselOutputFileAnnotation("foo"), ChiselOutputFileAnnotation("bar")) + checkExceptionMessage(phase, a, "At most one Chisel output file") + } + +} diff --git a/src/test/scala/chiselTests/stage/phases/ConvertSpec.scala b/src/test/scala/chiselTests/stage/phases/ConvertSpec.scala new file mode 100644 index 00000000..30fad4f5 --- /dev/null +++ b/src/test/scala/chiselTests/stage/phases/ConvertSpec.scala @@ -0,0 +1,62 @@ +// See LICENSE for license details. + +package chiselTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import chisel3._ +import chisel3.experimental.{ChiselAnnotation, RawModule, RunFirrtlTransform} +import chisel3.stage.ChiselGeneratorAnnotation +import chisel3.stage.phases.{Convert, Elaborate} + +import firrtl.{AnnotationSeq, CircuitForm, CircuitState, Transform, UnknownForm} +import firrtl.annotations.{Annotation, NoTargetAnnotation} +import firrtl.options.Phase +import firrtl.stage.{FirrtlCircuitAnnotation, RunFirrtlTransformAnnotation} + +class ConvertSpecFirrtlTransform extends Transform { + def inputForm: CircuitForm = UnknownForm + def outputForm: CircuitForm = UnknownForm + def execute(state: CircuitState): CircuitState = state +} + +case class ConvertSpecFirrtlAnnotation(name: String) extends NoTargetAnnotation + +case class ConvertSpecChiselAnnotation(name: String) extends ChiselAnnotation with RunFirrtlTransform { + def toFirrtl: Annotation = ConvertSpecFirrtlAnnotation(name) + def transformClass: Class[_ <: Transform] = classOf[ConvertSpecFirrtlTransform] +} + +class ConvertSpecFoo extends RawModule { + override val desiredName: String = "foo" + + val in = IO(Input(Bool())) + val out = IO(Output(Bool())) + + experimental.annotate(ConvertSpecChiselAnnotation("bar")) +} + +class ConvertSpec extends FlatSpec with Matchers { + + class Fixture { val phase: Phase = new Convert } + + behavior of classOf[Convert].toString + + it should "convert a Chisel Circuit to a FIRRTL Circuit" in new Fixture { + val annos: AnnotationSeq = Seq(ChiselGeneratorAnnotation(() => new ConvertSpecFoo)) + + val annosx = Seq(new Elaborate, phase) + .foldLeft(annos)( (a, p) => p.transform(a) ) + + info("FIRRTL circuit generated") + annosx.collect{ case a: FirrtlCircuitAnnotation => a.circuit.main }.toSeq should be (Seq("foo")) + + info("FIRRTL annotations generated") + annosx.collect{ case a: ConvertSpecFirrtlAnnotation => a.name }.toSeq should be (Seq("bar")) + + info("FIRRTL transform annotations generated") + annosx.collect{ case a: RunFirrtlTransformAnnotation => a.transform.getClass} + .toSeq should be (Seq(classOf[ConvertSpecFirrtlTransform])) + } + +} diff --git a/src/test/scala/chiselTests/stage/phases/ElaborateSpec.scala b/src/test/scala/chiselTests/stage/phases/ElaborateSpec.scala new file mode 100644 index 00000000..4d99b24c --- /dev/null +++ b/src/test/scala/chiselTests/stage/phases/ElaborateSpec.scala @@ -0,0 +1,46 @@ +// See LICENSE for license details. + +package chiselTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import chisel3._ +import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation} +import chisel3.stage.phases.Elaborate + +import firrtl.options.Phase + +class ElaborateSpec extends FlatSpec with Matchers { + + class Foo extends Module { + override def desiredName: String = "Foo" + val io = IO( + new Bundle { + val in = Input(Bool()) + val out = Output(Bool()) + }) + + io.out := ~io.in + } + + class Bar extends Foo { + override def desiredName: String = "Bar" + } + + class Fixture { val phase: Phase = new Elaborate } + + behavior of classOf[Elaborate].toString + + it should "expand ChiselGeneratorAnnotations into ChiselCircuitAnnotations and delete originals" in new Fixture { + val annotations = Seq( ChiselGeneratorAnnotation(() => new Foo), + ChiselGeneratorAnnotation(() => new Bar) ) + val out = phase.transform(annotations) + + info("original annotations removed") + out.collect{ case a: ChiselGeneratorAnnotation => a } should be (empty) + + info("circuits created with the expected names") + out.collect{ case a: ChiselCircuitAnnotation => a.circuit.name } should be (Seq("Foo", "Bar")) + } + +} diff --git a/src/test/scala/chiselTests/stage/phases/EmitterSpec.scala b/src/test/scala/chiselTests/stage/phases/EmitterSpec.scala new file mode 100644 index 00000000..63498adb --- /dev/null +++ b/src/test/scala/chiselTests/stage/phases/EmitterSpec.scala @@ -0,0 +1,60 @@ +// See LICENSE for license details. + +package chiselTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import chisel3.experimental.RawModule +import chisel3.stage.{ChiselCircuitAnnotation, ChiselGeneratorAnnotation, ChiselOutputFileAnnotation} +import chisel3.stage.phases.{Convert, Elaborate, Emitter} + +import firrtl.{AnnotationSeq, EmittedFirrtlCircuitAnnotation} +import firrtl.annotations.DeletedAnnotation +import firrtl.options.{Phase, TargetDirAnnotation} + +import java.io.File + +class EmitterSpec extends FlatSpec with Matchers { + + class FooModule extends RawModule { override val desiredName = "Foo" } + class BarModule extends RawModule { override val desiredName = "Bar" } + + class Fixture { val phase: Phase = new Emitter } + + behavior of classOf[Emitter].toString + + it should "do nothing if no ChiselOutputFileAnnotations are present" in new Fixture { + val dir = new File("test_run_dir/EmitterSpec") + val annotations = (new Elaborate).transform(Seq( TargetDirAnnotation(dir.toString), + ChiselGeneratorAnnotation(() => new FooModule) )) + val annotationsx = phase.transform(annotations) + + val Seq(fooFile, barFile) = Seq("Foo.fir", "Bar.fir").map(f => new File(dir + "/" + f)) + + info(s"$fooFile does not exist") + fooFile should not (exist) + + info("annotations are unmodified") + annotationsx.toSeq should be (annotations.toSeq) + } + + it should "emit a ChiselCircuitAnnotation to a specific file" in new Fixture { + val dir = new File("test_run_dir/EmitterSpec") + val circuit = (new Elaborate) + .transform(Seq(ChiselGeneratorAnnotation(() => new BarModule))) + .collectFirst{ case a: ChiselCircuitAnnotation => a} + .get + val annotations = phase.transform(Seq( TargetDirAnnotation(dir.toString), + circuit, + ChiselOutputFileAnnotation("Baz") )) + + val bazFile = new File(dir + "/Baz.fir") + + info(s"$bazFile exists") + bazFile should (exist) + + info("a deleted EmittedFirrtlCircuitAnnotation should be generated") + annotations.collect{ case a @ DeletedAnnotation(_, _: EmittedFirrtlCircuitAnnotation) => a }.size should be (1) + } + +} |
