diff options
| author | Jack Koenig | 2019-04-26 13:10:44 -0700 |
|---|---|---|
| committer | GitHub | 2019-04-26 13:10:44 -0700 |
| commit | a7cf6ff3416a11088d811a435ba71fd36b191fb4 (patch) | |
| tree | 79e2e8c5753903ca6d14e9b952c26a07442bd980 /src | |
| parent | 99ae1d6649f1731c5dec2098b10733735232b72c (diff) | |
| parent | ef8f06f23b9ee6cf86de2450752dfd0fcd32da80 (diff) | |
Merge pull request #1005 from freechipsproject/f764.7
Stage/Phase
Diffstat (limited to 'src')
74 files changed, 3759 insertions, 666 deletions
diff --git a/src/main/scala/firrtl/Compiler.scala b/src/main/scala/firrtl/Compiler.scala index 1ef35891..a15ca6cb 100644 --- a/src/main/scala/firrtl/Compiler.scala +++ b/src/main/scala/firrtl/Compiler.scala @@ -14,6 +14,7 @@ import firrtl.Utils.{error, throwInternalError} import firrtl.annotations.TargetToken import firrtl.annotations.TargetToken.{Field, Index} import firrtl.annotations.transforms.{EliminateTargetPaths, ResolvePaths} +import firrtl.options.StageUtils /** Container of all annotations for a Firrtl compiler */ class AnnotationSeq private (private[firrtl] val underlying: List[Annotation]) { @@ -101,6 +102,9 @@ object CircuitState { sealed abstract class CircuitForm(private val value: Int) extends Ordered[CircuitForm] { // Note that value is used only to allow comparisons def compare(that: CircuitForm): Int = this.value - that.value + + /** Defines a suffix to use if this form is written to a file */ + def outputSuffix: String } // scalastyle:off magic.number @@ -113,7 +117,10 @@ sealed abstract class CircuitForm(private val value: Int) extends Ordered[Circui * * See [[CDefMemory]] and [[CDefMPort]] */ -final case object ChirrtlForm extends CircuitForm(value = 3) +final case object ChirrtlForm extends CircuitForm(value = 3) { + val outputSuffix: String = ".fir" +} + /** High Form * * As detailed in the Firrtl specification @@ -121,7 +128,10 @@ final case object ChirrtlForm extends CircuitForm(value = 3) * * Also see [[firrtl.ir]] */ -final case object HighForm extends CircuitForm(2) +final case object HighForm extends CircuitForm(2) { + val outputSuffix: String = ".hi.fir" +} + /** Middle Form * * A "lower" form than [[HighForm]] with the following restrictions: @@ -129,14 +139,20 @@ final case object HighForm extends CircuitForm(2) * - All whens must be removed * - There can only be a single connection to any element */ -final case object MidForm extends CircuitForm(1) +final case object MidForm extends CircuitForm(1) { + val outputSuffix: String = ".mid.fir" +} + /** Low Form * * The "lowest" form. In addition to the restrictions in [[MidForm]]: * - All aggregate types (vector/bundle) must have been removed * - All implicit truncations must be made explicit */ -final case object LowForm extends CircuitForm(0) +final case object LowForm extends CircuitForm(0) { + val outputSuffix: String = ".lo.fir" +} + /** Unknown Form * * Often passes may modify a circuit (e.g. InferTypes), but return @@ -150,6 +166,8 @@ final case object LowForm extends CircuitForm(0) */ final case object UnknownForm extends CircuitForm(-1) { override def compare(that: CircuitForm): Int = { sys.error("Illegal to compare UnknownForm"); 0 } + + val outputSuffix: String = ".unknown.fir" } // scalastyle:on magic.number @@ -178,7 +196,7 @@ abstract class Transform extends LazyLogging { @deprecated("Just collect the actual Annotation types the transform wants", "1.1") final def getMyAnnotations(state: CircuitState): Seq[Annotation] = { val msg = "getMyAnnotations is deprecated, use collect and match on concrete types" - Driver.dramaticWarning(msg) + StageUtils.dramaticWarning(msg) state.annotations.collect { case a: LegacyAnnotation if a.transform == this.getClass => a } } @@ -293,6 +311,9 @@ trait ResolvedAnnotationPaths { trait Emitter extends Transform { @deprecated("Use emission annotations instead", "firrtl 1.0") def emit(state: CircuitState, writer: Writer): Unit + + /** An output suffix to use if the output of this [[Emitter]] was written to a file */ + def outputSuffix: String } /** Wraps exceptions from CustomTransforms so they can be reported appropriately */ @@ -325,11 +346,10 @@ object CompilerUtils extends LazyLogging { /** Merge a Seq of lowering transforms with custom transforms * - * Custom Transforms are inserted based on their [[Transform.inputForm]] and - * [[Transform.outputForm]]. Custom transforms are inserted in order at the - * last location in the Seq of transforms where previous.outputForm == - * customTransform.inputForm. If a customTransform outputs a higher form - * than input, [[getLoweringTransforms]] is used to relower the circuit. + * Custom Transforms are inserted based on their [[Transform.inputForm]] and [[Transform.outputForm]] with any + * [[Emitter]]s being scheduled last. Custom transforms are inserted in order at the last location in the Seq of + * transforms where previous.outputForm == customTransform.inputForm. If a customTransform outputs a higher form than + * input, [[getLoweringTransforms]] is used to relower the circuit. * * @example * {{{ @@ -356,7 +376,13 @@ object CompilerUtils extends LazyLogging { * of the previous transform. */ def mergeTransforms(lowering: Seq[Transform], custom: Seq[Transform]): Seq[Transform] = { - custom.foldLeft(lowering) { case (transforms, xform) => + custom + .sortWith{ + case (a, b) => (a, b) match { + case (_: Emitter, _: Emitter) => false + case (_, _: Emitter) => true + case _ => false }} + .foldLeft(lowering) { case (transforms, xform) => val index = transforms lastIndexWhere (_.outputForm == xform.inputForm) assert(index >= 0 || xform.inputForm == ChirrtlForm, // If ChirrtlForm just put at front s"No transform in $lowering has outputForm ${xform.inputForm} as required by $xform") @@ -427,7 +453,7 @@ trait Compiler extends LazyLogging { def compileAndEmit(state: CircuitState, customTransforms: Seq[Transform] = Seq.empty): CircuitState = { val emitAnno = EmitCircuitAnnotation(emitter.getClass) - compile(state.copy(annotations = emitAnno +: state.annotations), customTransforms) + compile(state.copy(annotations = emitAnno +: state.annotations), emitter +: customTransforms) } private def isCustomTransform(xform: Transform): Boolean = { @@ -449,7 +475,7 @@ trait Compiler extends LazyLogging { * @return result of compilation */ def compile(state: CircuitState, customTransforms: Seq[Transform]): CircuitState = { - val allTransforms = CompilerUtils.mergeTransforms(transforms, customTransforms) :+ emitter + val allTransforms = CompilerUtils.mergeTransforms(transforms, customTransforms) val (timeMillis, finalState) = Utils.time { allTransforms.foldLeft(state) { (in, xform) => @@ -468,4 +494,3 @@ trait Compiler extends LazyLogging { } } - diff --git a/src/main/scala/firrtl/Driver.scala b/src/main/scala/firrtl/Driver.scala index e96c3c5b..66e09e86 100644 --- a/src/main/scala/firrtl/Driver.scala +++ b/src/main/scala/firrtl/Driver.scala @@ -4,11 +4,10 @@ package firrtl import scala.collection._ import scala.io.Source -import scala.sys.process.{BasicIO, ProcessLogger, stringSeqToProcess} import scala.util.{Failure, Success, Try} import scala.util.control.ControlThrowable import java.io.{File, FileNotFoundException} - +import scala.sys.process.{BasicIO, ProcessLogger, stringSeqToProcess} import net.jcazevedo.moultingyaml._ import logger.Logger import Parser.{IgnoreInfo, InfoMode} @@ -17,6 +16,10 @@ import firrtl.annotations.AnnotationYamlProtocol._ import firrtl.passes.{PassException, PassExceptions} import firrtl.transforms._ import firrtl.Utils.throwInternalError +import firrtl.stage.{FirrtlExecutionResultView, FirrtlStage} +import firrtl.stage.phases.DriverCompatibility +import firrtl.options.{StageUtils, Phase, Viewer} +import firrtl.options.phases.DeletedWrapper /** @@ -40,30 +43,22 @@ import firrtl.Utils.throwInternalError * @see firrtlTests/DriverSpec.scala in the test directory for a lot more examples * @see [[CompilerUtils.mergeTransforms]] to see how customTransformations are inserted */ - +@deprecated("Use firrtl.stage.FirrtlStage", "1.2") object Driver { /** Print a warning message * * @param message error message */ - //scalastyle:off regex - def dramaticWarning(message: String): Unit = { - println(Console.YELLOW + "-"*78) - println(s"Warning: $message") - println("-"*78 + Console.RESET) - } + @deprecated("Use firrtl.options.StageUtils.dramaticWarning", "1.2") + def dramaticWarning(message: String): Unit = StageUtils.dramaticWarning(message) /** * print the message in red * * @param message error message */ - //scalastyle:off regex - def dramaticError(message: String): Unit = { - println(Console.RED + "-"*78) - println(s"Error: $message") - println("-"*78 + Console.RESET) - } + @deprecated("Use firrtl.options.StageUtils.dramaticWarning", "1.2") + def dramaticError(message: String): Unit = StageUtils.dramaticError(message) /** Load annotation file based on options * @param optionsManager use optionsManager config to load annotation file if it exists @@ -107,7 +102,7 @@ object Driver { if (firrtlConfig.annotationFileNameOverride.nonEmpty) { val msg = "annotationFileNameOverride is deprecated! " + "Use annotationFileNames" - Driver.dramaticWarning(msg) + dramaticWarning(msg) } else if (usingImplicitAnnoFile) { val msg = "Implicit .anno file from top-name is deprecated!\n" + (" "*9) + "Use explicit -faf option or annotationFileNames" @@ -159,7 +154,7 @@ object Driver { } // Useful for handling erros in the options - case class OptionsException(msg: String) extends Exception(msg) + case class OptionsException(message: String) extends Exception(message) /** Get the Circuit from the compile options * @@ -216,77 +211,26 @@ object Driver { * @return a FirrtlExecutionResult indicating success or failure, provide access to emitted data on success * for downstream tools as desired */ - //scalastyle:off cyclomatic.complexity method.length def execute(optionsManager: ExecutionOptionsManager with HasFirrtlOptions): FirrtlExecutionResult = { - def firrtlConfig = optionsManager.firrtlOptions - - Logger.makeScope(optionsManager) { - // Wrap compilation in a try/catch to present Scala MatchErrors in a more user-friendly format. - val finalState = try { - val circuit = getCircuit(optionsManager) match { - case Success(c) => c - case Failure(OptionsException(msg)) => - dramaticError(msg) - return FirrtlExecutionFailure(msg) - case Failure(e) => throw e - } + StageUtils.dramaticWarning("firrtl.Driver is deprecated since 1.2!\nPlease switch to firrtl.stage.FirrtlStage") - val annos = getAnnotations(optionsManager) + val annos = optionsManager.firrtlOptions.toAnnotations ++ optionsManager.commonOptions.toAnnotations - // Does this need to be before calling compiler? - optionsManager.makeTargetDir() + val phases: Seq[Phase] = + Seq( new DriverCompatibility.AddImplicitAnnotationFile, + new DriverCompatibility.AddImplicitFirrtlFile, + new DriverCompatibility.AddImplicitOutputFile, + new DriverCompatibility.AddImplicitEmitter, + new FirrtlStage ) + .map(DeletedWrapper(_)) - firrtlConfig.compiler.compile( - CircuitState(circuit, ChirrtlForm, annos), - firrtlConfig.customTransforms - ) - } - catch { - // Rethrow the exceptions which are expected or due to the runtime environment (out of memory, stack overflow) - case p: ControlThrowable => throw p - case p: PassException => throw p - case p: PassExceptions => throw p - case p: FIRRTLException => throw p - // Propagate exceptions from custom transforms - case CustomTransformException(cause) => throw cause - // Treat remaining exceptions as internal errors. - case e: Exception => throwInternalError(exception = Some(e)) - } - - // Do emission - // Note: Single emission target assumption is baked in here - // Note: FirrtlExecutionSuccess emitted is only used if we're emitting the whole Circuit - val emittedRes = firrtlConfig.getOutputConfig(optionsManager) match { - case SingleFile(filename) => - val emitted = finalState.getEmittedCircuit - val outputFile = new java.io.PrintWriter(filename) - outputFile.write(emitted.value) - outputFile.close() - emitted.value - case OneFilePerModule(dirName) => - val emittedModules = finalState.emittedComponents collect { case x: EmittedModule => x } - if (emittedModules.isEmpty) throwInternalError() // There should be something - emittedModules.foreach { module => - val filename = optionsManager.getBuildFileName(firrtlConfig.outputSuffix, s"$dirName/${module.name}") - val outputFile = new java.io.PrintWriter(filename) - outputFile.write(module.value) - outputFile.close() - } - "" // Should we return something different here? - } - - // If set, emit final annotations to a file - optionsManager.firrtlOptions.outputAnnotationFileName match { - case "" => - case file => - val filename = optionsManager.getBuildFileName("anno.json", file) - val outputFile = new java.io.PrintWriter(filename) - outputFile.write(JsonProtocol.serialize(finalState.annotations)) - outputFile.close() - } - - FirrtlExecutionSuccess(firrtlConfig.compilerName, emittedRes, finalState) + val annosx = try { + phases.foldLeft(annos)( (a, p) => p.transform(a) ) + } catch { + case e: firrtl.options.OptionsException => return FirrtlExecutionFailure(e.message) } + + Viewer[FirrtlExecutionResult].view(annosx) } /** @@ -321,23 +265,16 @@ object Driver { } object FileUtils { - /** - * recursive create directory and all parents - * + + /** Create a directory if it doesn't exist * @param directoryName a directory string with one or more levels - * @return + * @return true if the directory exists or if it was successfully created */ def makeDirectory(directoryName: String): Boolean = { - val dirFile = new java.io.File(directoryName) + val dirFile = new File(directoryName) if(dirFile.exists()) { - if(dirFile.isDirectory) { - true - } - else { - false - } - } - else { + dirFile.isDirectory + } else { dirFile.mkdirs() } } @@ -361,7 +298,7 @@ object FileUtils { if(file.getPath.split("/").last.isEmpty || file.getAbsolutePath == "/" || file.getPath.startsWith("/")) { - Driver.dramaticError(s"delete directory ${file.getPath} will not delete absolute paths") + StageUtils.dramaticError(s"delete directory ${file.getPath} will not delete absolute paths") false } else { diff --git a/src/main/scala/firrtl/Emitter.scala b/src/main/scala/firrtl/Emitter.scala index 7204eea6..3bbba289 100644 --- a/src/main/scala/firrtl/Emitter.scala +++ b/src/main/scala/firrtl/Emitter.scala @@ -19,6 +19,9 @@ import firrtl.PrimOps._ import firrtl.WrappedExpression._ import Utils._ import MemPortUtils.{memPortField, memType} +import firrtl.options.{HasShellOptions, ShellOption, StageUtils, PhaseException} +import firrtl.stage.RunFirrtlTransformAnnotation +import scopt.OptionParser // Datastructures import scala.collection.mutable.{ArrayBuffer, LinkedHashMap, HashSet} @@ -31,17 +34,68 @@ sealed trait EmitAnnotation extends NoTargetAnnotation { case class EmitCircuitAnnotation(emitter: Class[_ <: Emitter]) extends EmitAnnotation case class EmitAllModulesAnnotation(emitter: Class[_ <: Emitter]) extends EmitAnnotation +object EmitCircuitAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "emit-circuit", + toAnnotationSeq = (a: String) => a match { + case "chirrtl" => Seq(RunFirrtlTransformAnnotation(new ChirrtlEmitter), + EmitCircuitAnnotation(classOf[ChirrtlEmitter])) + case "high" => Seq(RunFirrtlTransformAnnotation(new HighFirrtlEmitter), + EmitCircuitAnnotation(classOf[HighFirrtlEmitter])) + case "middle" => Seq(RunFirrtlTransformAnnotation(new MiddleFirrtlEmitter), + EmitCircuitAnnotation(classOf[MiddleFirrtlEmitter])) + case "low" => Seq(RunFirrtlTransformAnnotation(new LowFirrtlEmitter), + EmitCircuitAnnotation(classOf[LowFirrtlEmitter])) + case "verilog" | "mverilog" => Seq(RunFirrtlTransformAnnotation(new VerilogEmitter), + EmitCircuitAnnotation(classOf[VerilogEmitter])) + case "sverilog" => Seq(RunFirrtlTransformAnnotation(new SystemVerilogEmitter), + EmitCircuitAnnotation(classOf[SystemVerilogEmitter])) + case _ => throw new PhaseException(s"Unknown emitter '$a'! (Did you misspell it?)") }, + helpText = "Run the specified circuit emitter (all modules in one file)", + shortOption = Some("E"), + helpValueName = Some("<chirrtl|high|middle|low|verilog|mverilog|sverilog>") ) ) + +} + +object EmitAllModulesAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "emit-modules", + toAnnotationSeq = (a: String) => a match { + case "chirrtl" => Seq(RunFirrtlTransformAnnotation(new ChirrtlEmitter), + EmitAllModulesAnnotation(classOf[ChirrtlEmitter])) + case "high" => Seq(RunFirrtlTransformAnnotation(new HighFirrtlEmitter), + EmitAllModulesAnnotation(classOf[HighFirrtlEmitter])) + case "middle" => Seq(RunFirrtlTransformAnnotation(new MiddleFirrtlEmitter), + EmitAllModulesAnnotation(classOf[MiddleFirrtlEmitter])) + case "low" => Seq(RunFirrtlTransformAnnotation(new LowFirrtlEmitter), + EmitAllModulesAnnotation(classOf[LowFirrtlEmitter])) + case "verilog" | "mverilog" => Seq(RunFirrtlTransformAnnotation(new VerilogEmitter), + EmitAllModulesAnnotation(classOf[VerilogEmitter])) + case "sverilog" => Seq(RunFirrtlTransformAnnotation(new SystemVerilogEmitter), + EmitAllModulesAnnotation(classOf[SystemVerilogEmitter])) + case _ => throw new PhaseException(s"Unknown emitter '$a'! (Did you misspell it?)") }, + helpText = "Run the specified module emitter (one file per module)", + shortOption = Some("e"), + helpValueName = Some("<chirrtl|high|middle|low|verilog|mverilog|sverilog>") ) ) + +} + // ***** Annotations for results of emission ***** sealed abstract class EmittedComponent { def name: String def value: String + def outputSuffix: String } sealed abstract class EmittedCircuit extends EmittedComponent -final case class EmittedFirrtlCircuit(name: String, value: String) extends EmittedCircuit -final case class EmittedVerilogCircuit(name: String, value: String) extends EmittedCircuit +final case class EmittedFirrtlCircuit(name: String, value: String, outputSuffix: String) extends EmittedCircuit +final case class EmittedVerilogCircuit(name: String, value: String, outputSuffix: String) extends EmittedCircuit sealed abstract class EmittedModule extends EmittedComponent -final case class EmittedFirrtlModule(name: String, value: String) extends EmittedModule -final case class EmittedVerilogModule(name: String, value: String) extends EmittedModule +final case class EmittedFirrtlModule(name: String, value: String, outputSuffix: String) extends EmittedModule +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 { @@ -51,19 +105,21 @@ sealed trait EmittedCircuitAnnotation[T <: EmittedCircuit] extends EmittedAnnota sealed trait EmittedModuleAnnotation[T <: EmittedModule] extends EmittedAnnotation[T] case class EmittedFirrtlCircuitAnnotation(value: EmittedFirrtlCircuit) - extends EmittedCircuitAnnotation[EmittedFirrtlCircuit] + extends EmittedCircuitAnnotation[EmittedFirrtlCircuit] case class EmittedVerilogCircuitAnnotation(value: EmittedVerilogCircuit) - extends EmittedCircuitAnnotation[EmittedVerilogCircuit] + extends EmittedCircuitAnnotation[EmittedVerilogCircuit] case class EmittedFirrtlModuleAnnotation(value: EmittedFirrtlModule) - extends EmittedModuleAnnotation[EmittedFirrtlModule] + extends EmittedModuleAnnotation[EmittedFirrtlModule] case class EmittedVerilogModuleAnnotation(value: EmittedVerilogModule) - extends EmittedModuleAnnotation[EmittedVerilogModule] + extends EmittedModuleAnnotation[EmittedVerilogModule] sealed abstract class FirrtlEmitter(form: CircuitForm) extends Transform with Emitter { def inputForm = form def outputForm = form + val outputSuffix: String = form.outputSuffix + private def emitAllModules(circuit: Circuit): Seq[EmittedFirrtlModule] = { // For a given module, returns a Seq of all modules instantited inside of it def collectInstantiatedModules(mod: Module, map: Map[String, DefModule]): Seq[DefModule] = { @@ -87,15 +143,15 @@ sealed abstract class FirrtlEmitter(form: CircuitForm) extends Transform with Em case ext: ExtModule => ext } val newCircuit = Circuit(m.info, extModules :+ m, m.name) - EmittedFirrtlModule(m.name, newCircuit.serialize) + EmittedFirrtlModule(m.name, newCircuit.serialize, outputSuffix) } } override def execute(state: CircuitState): CircuitState = { val newAnnos = state.annotations.flatMap { case EmitCircuitAnnotation(_) => - Seq(EmittedFirrtlCircuitAnnotation.apply( - EmittedFirrtlCircuit(state.circuit.main, state.circuit.serialize))) + Seq(EmittedFirrtlCircuitAnnotation( + EmittedFirrtlCircuit(state.circuit.main, state.circuit.serialize, outputSuffix))) case EmitAllModulesAnnotation(_) => emitAllModules(state.circuit) map (EmittedFirrtlModuleAnnotation(_)) case _ => Seq() @@ -129,6 +185,7 @@ case class VRandom(width: BigInt) extends Expression { class VerilogEmitter extends SeqTransform with Emitter { def inputForm = LowForm def outputForm = LowForm + val outputSuffix = ".v" val tab = " " def AND(e1: WrappedExpression, e2: WrappedExpression): Expression = { if (e1 == e2) e1.e1 @@ -363,8 +420,8 @@ class VerilogEmitter extends SeqTransform with Emitter { * @return the render reference */ def getRenderer(descriptions: Seq[DescriptionAnnotation], - m: Module, - moduleMap: Map[String, DefModule])(implicit writer: Writer): VerilogRender = { + m: Module, + moduleMap: Map[String, DefModule])(implicit writer: Writer): VerilogRender = { val newMod = new AddDescriptionNodes().executeModule(m, descriptions) newMod match { @@ -384,9 +441,9 @@ class VerilogEmitter extends SeqTransform with Emitter { * @param writer where rendered information is placed. */ class VerilogRender(description: Description, - portDescriptions: Map[String, Description], - m: Module, - moduleMap: Map[String, DefModule])(implicit writer: Writer) { + portDescriptions: Map[String, Description], + m: Module, + moduleMap: Map[String, DefModule])(implicit writer: Writer) { def this(m: Module, moduleMap: Map[String, DefModule])(implicit writer: Writer) { this(EmptyDescription, Map.empty, m, moduleMap)(writer) @@ -458,7 +515,7 @@ class VerilogEmitter extends SeqTransform with Emitter { assigns += Seq("assign ", e, " = ", syn, ";", info) assigns += Seq("`else") assigns += Seq("assign ", e, " = ", garbageCond, " ? ", rand_string(syn.tpe), " : ", syn, - ";", info) + ";", info) assigns += Seq("`endif // RANDOMIZE_GARBAGE_ASSIGN") } @@ -550,7 +607,7 @@ class VerilogEmitter extends SeqTransform with Emitter { initials += Seq("`ifdef RANDOMIZE_MEM_INIT") initials += Seq("for (initvar = 0; initvar < ", bigIntToVLit(s.depth), "; initvar = initvar+1)") initials += Seq(tab, WSubAccess(wref(s.name, s.dataType), index, s.dataType, FEMALE), - " = ", rstring, ";") + " = ", rstring, ";") initials += Seq("`endif // RANDOMIZE_MEM_INIT") } @@ -696,14 +753,14 @@ class VerilogEmitter extends SeqTransform with Emitter { instdeclares += Seq(");") case sx: DefMemory => val fullSize = sx.depth * (sx.dataType match { - case GroundType(IntWidth(width)) => width - }) + case GroundType(IntWidth(width)) => width + }) val decl = if (fullSize > (1 << 29)) "reg /* sparse */" else "reg" declareVectorType(decl, sx.name, sx.dataType, sx.depth, sx.info) initialize_mem(sx) if (sx.readLatency != 0 || sx.writeLatency != 1) throw EmitterException("All memories should be transformed into " + - "blackboxes or combinational by previous passses") + "blackboxes or combinational by previous passses") for (r <- sx.readers) { val data = memPortField(sx, r, "data") val addr = memPortField(sx, r, "addr") @@ -717,7 +774,7 @@ class VerilogEmitter extends SeqTransform with Emitter { //; Read port assign(addr, netlist(addr), NoInfo) // Info should come from addr connection - // assign(en, netlist(en)) //;Connects value to m.r.en + // assign(en, netlist(en)) //;Connects value to m.r.en val mem = WRef(sx.name, memType(sx), MemKind, UNKNOWNGENDER) val memPort = WSubAccess(mem, addr, sx.dataType, UNKNOWNGENDER) val depthValue = UIntLiteral(sx.depth, IntWidth(sx.depth.bitLength)) @@ -756,7 +813,7 @@ class VerilogEmitter extends SeqTransform with Emitter { if (sx.readwriters.nonEmpty) throw EmitterException("All readwrite ports should be transformed into " + - "read & write ports by previous passes") + "read & write ports by previous passes") case _ => } } @@ -918,7 +975,7 @@ class VerilogEmitter extends SeqTransform with Emitter { case EmitCircuitAnnotation(_) => val writer = new java.io.StringWriter emit(state, writer) - Seq(EmittedVerilogCircuitAnnotation(EmittedVerilogCircuit(state.circuit.main, writer.toString))) + Seq(EmittedVerilogCircuitAnnotation(EmittedVerilogCircuit(state.circuit.main, writer.toString, outputSuffix))) case EmitAllModulesAnnotation(_) => val circuit = runTransforms(state).circuit @@ -929,12 +986,12 @@ class VerilogEmitter extends SeqTransform with Emitter { val writer = new java.io.StringWriter val renderer = new VerilogRender(d, pds, module, moduleMap)(writer) renderer.emit_verilog() - Some(EmittedVerilogModuleAnnotation(EmittedVerilogModule(module.name, writer.toString))) + Some(EmittedVerilogModuleAnnotation(EmittedVerilogModule(module.name, writer.toString, outputSuffix))) case module: Module => val writer = new java.io.StringWriter val renderer = new VerilogRender(module, moduleMap)(writer) renderer.emit_verilog() - Some(EmittedVerilogModuleAnnotation(EmittedVerilogModule(module.name, writer.toString))) + Some(EmittedVerilogModuleAnnotation(EmittedVerilogModule(module.name, writer.toString, outputSuffix))) case _ => None } case _ => Seq() @@ -952,3 +1009,9 @@ class MinimumVerilogEmitter extends VerilogEmitter with Emitter { } } + +class SystemVerilogEmitter extends VerilogEmitter { + StageUtils.dramaticWarning("SystemVerilog Emitter is the same as the Verilog Emitter!") + + override val outputSuffix: String = ".sv" +} diff --git a/src/main/scala/firrtl/ExecutionOptionsManager.scala b/src/main/scala/firrtl/ExecutionOptionsManager.scala index 189701dc..3783db28 100644 --- a/src/main/scala/firrtl/ExecutionOptionsManager.scala +++ b/src/main/scala/firrtl/ExecutionOptionsManager.scala @@ -9,7 +9,13 @@ import firrtl.passes.memlib.{InferReadWriteAnnotation, ReplSeqMemAnnotation} import firrtl.passes.clocklist.ClockListAnnotation import firrtl.transforms.NoCircuitDedupAnnotation import logger.LogLevel +import logger.{ClassLogLevelAnnotation, LogClassNamesAnnotation, LogFileAnnotation, LogLevelAnnotation} import scopt.OptionParser +import firrtl.stage.{CompilerAnnotation, FirrtlCircuitAnnotation, FirrtlFileAnnotation, FirrtlSourceAnnotation, + InfoModeAnnotation, OutputFileAnnotation, RunFirrtlTransformAnnotation} +import firrtl.stage.phases.DriverCompatibility.{TopNameAnnotation, EmitOneFilePerModuleAnnotation} +import firrtl.options.{InputAnnotationFileAnnotation, OutputAnnotationFileAnnotation, ProgramArgsAnnotation, StageUtils} +import firrtl.transforms.{DontCheckCombLoopsAnnotation, NoDCEAnnotation} import scala.collection.Seq @@ -19,8 +25,10 @@ import scala.collection.Seq * '''NOTE''' In all derived trait/classes, if you intend on maintaining backwards compatibility, * be sure to add new options at the end of the current ones and don't remove any existing ones. */ +@deprecated("Use firrtl.options.HasScoptOptions and/or library/transform registration", "1.2") trait ComposableOptions +@deprecated("Use firrtl.options.{ExecutionOptionsManager, TerminateOnExit, DuplicateHandling}", "1.2") abstract class HasParser(applicationName: String) { final val parser = new OptionParser[Unit](applicationName) { var terminateOnExit = true @@ -49,6 +57,7 @@ abstract class HasParser(applicationName: String) { * For example, in chisel, by deferring this it is possible for the execute there to first elaborate the * circuit and then set the topName from that if it has not already been set. */ +@deprecated("Use a FirrtlOptionsView, LoggerOptionsView, or construct your own view of an AnnotationSeq", "1.2") case class CommonOptions( topName: String = "", targetDirName: String = ".", @@ -67,8 +76,17 @@ case class CommonOptions( optionsManager.getBuildFileName("log") } } + + def toAnnotations: AnnotationSeq = (if (topName.nonEmpty) Seq(TopNameAnnotation(topName)) else Seq()) ++ + (if (targetDirName != ".") Some(TargetDirAnnotation(targetDirName)) else None) ++ + Some(LogLevelAnnotation(globalLogLevel)) ++ + (if (logToFile) { Some(LogFileAnnotation(None)) } else { None }) ++ + (if (logClassNames) { Some(LogClassNamesAnnotation) } else { None }) ++ + classLogLevels.map{ case (c, v) => ClassLogLevelAnnotation(c, v) } ++ + programArgs.map( a => ProgramArgsAnnotation(a) ) } +@deprecated("Specify command line arguments in an Annotation mixing in HasScoptOptions", "1.2") trait HasCommonOptions { self: ExecutionOptionsManager => var commonOptions = CommonOptions() @@ -169,6 +187,7 @@ final case class OneFilePerModule(targetDir: String) extends OutputConfig * @param compilerName which compiler to use * @param annotations annotations to pass to compiler */ +@deprecated("Use a FirrtlOptionsView or construct your own view of an AnnotationSeq", "1.2") case class FirrtlExecutionOptions( inputFileNameOverride: String = "", outputFileNameOverride: String = "", @@ -287,8 +306,31 @@ extends ComposableOptions { def getAnnotationFileName(optionsManager: ExecutionOptionsManager): String = { optionsManager.getBuildFileName("anno", annotationFileNameOverride) } + + def toAnnotations: AnnotationSeq = { + if (inferRW.nonEmpty) { + StageUtils.dramaticWarning("User set FirrtlExecutionOptions.inferRW, but inferRW has no effect!") + } + + (if (inputFileNameOverride.nonEmpty) Seq(FirrtlFileAnnotation(inputFileNameOverride)) else Seq()) ++ + (if (outputFileNameOverride.nonEmpty) { Some(OutputFileAnnotation(outputFileNameOverride)) } else { None }) ++ + Some(CompilerAnnotation(compilerName)) ++ + Some(InfoModeAnnotation(infoModeName)) ++ + firrtlSource.map(FirrtlSourceAnnotation(_)) ++ + customTransforms.map(t => RunFirrtlTransformAnnotation(t)) ++ + annotations ++ + (if (annotationFileNameOverride.nonEmpty) { Some(InputAnnotationFileAnnotation(annotationFileNameOverride)) } else { None }) ++ + (if (outputAnnotationFileName.nonEmpty) { Some(OutputAnnotationFileAnnotation(outputAnnotationFileName)) } else { None }) ++ + (if (emitOneFilePerModule) { Some(EmitOneFilePerModuleAnnotation) } else { None }) ++ + (if (dontCheckCombLoops) { Some(DontCheckCombLoopsAnnotation) } else { None }) ++ + (if (noDCE) { Some(NoDCEAnnotation) } else { None }) ++ + annotationFileNames.map(InputAnnotationFileAnnotation(_)) ++ + firrtlCircuit.map(FirrtlCircuitAnnotation(_)) + } } + +@deprecated("Specify command line arguments in an Annotation mixing in HasScoptOptions", "1.2") trait HasFirrtlOptions { self: ExecutionOptionsManager => var firrtlOptions = FirrtlExecutionOptions() @@ -333,7 +375,7 @@ trait HasFirrtlOptions { .foreach { _ => val msg = "force-append-anno-file is deprecated and will soon be removed\n" + (" "*9) + "(It does not do anything anymore)" - Driver.dramaticWarning(msg) + StageUtils.dramaticWarning(msg) } parser.opt[String]("output-annotation-file") @@ -489,8 +531,10 @@ trait HasFirrtlOptions { parser.note("") } +@deprecated("Use FirrtlStage and examine the output AnnotationSeq directly", "1.2") sealed trait FirrtlExecutionResult +@deprecated("Use FirrtlStage and examine the output AnnotationSeq directly", "1.2") object FirrtlExecutionSuccess { def apply( emitType : String, @@ -511,6 +555,7 @@ object FirrtlExecutionSuccess { * "sverilog" * @param emitted The emitted result of compilation */ +@deprecated("Use FirrtlStage and examine the output AnnotationSeq directly", "1.2") class FirrtlExecutionSuccess( val emitType: String, val emitted : String, @@ -522,12 +567,14 @@ class FirrtlExecutionSuccess( * * @param message Some kind of hint as to what went wrong. */ +@deprecated("Use FirrtlStage and examine the output AnnotationSeq directly", "1.2") case class FirrtlExecutionFailure(message: String) extends FirrtlExecutionResult /** * * @param applicationName The name shown in the usage */ +@deprecated("Use new FirrtlStage infrastructure", "1.2") class ExecutionOptionsManager(val applicationName: String) extends HasParser(applicationName) with HasCommonOptions { def parse(args: Array[String]): Boolean = { diff --git a/src/main/scala/firrtl/LoweringCompilers.scala b/src/main/scala/firrtl/LoweringCompilers.scala index 262caeea..5bece9fe 100644 --- a/src/main/scala/firrtl/LoweringCompilers.scala +++ b/src/main/scala/firrtl/LoweringCompilers.scala @@ -3,6 +3,7 @@ package firrtl import firrtl.transforms.IdentityTransform +import firrtl.options.StageUtils sealed abstract class CoreTransform extends SeqTransform @@ -134,7 +135,7 @@ import firrtl.transforms.BlackBoxSourceHelper * Primarily useful for changing between .fir and .pb serialized formats */ class NoneCompiler extends Compiler { - def emitter = new ChirrtlEmitter + val emitter = new ChirrtlEmitter def transforms: Seq[Transform] = Seq(new IdentityTransform(ChirrtlForm)) } @@ -142,37 +143,38 @@ class NoneCompiler extends Compiler { * Will replace Chirrtl constructs with Firrtl */ class HighFirrtlCompiler extends Compiler { - def emitter = new HighFirrtlEmitter + val emitter = new HighFirrtlEmitter def transforms: Seq[Transform] = getLoweringTransforms(ChirrtlForm, HighForm) } /** Emits middle Firrtl input circuit */ class MiddleFirrtlCompiler extends Compiler { - def emitter = new MiddleFirrtlEmitter + val emitter = new MiddleFirrtlEmitter def transforms: Seq[Transform] = getLoweringTransforms(ChirrtlForm, MidForm) } /** Emits lowered input circuit */ class LowFirrtlCompiler extends Compiler { - def emitter = new LowFirrtlEmitter + val emitter = new LowFirrtlEmitter def transforms: Seq[Transform] = getLoweringTransforms(ChirrtlForm, LowForm) } /** Emits Verilog */ class VerilogCompiler extends Compiler { - def emitter = new VerilogEmitter + val emitter = new VerilogEmitter def transforms: Seq[Transform] = getLoweringTransforms(ChirrtlForm, LowForm) ++ Seq(new LowFirrtlOptimization) } /** Emits Verilog without optimizations */ class MinimumVerilogCompiler extends Compiler { - def emitter = new MinimumVerilogEmitter + val emitter = new MinimumVerilogEmitter def transforms: Seq[Transform] = getLoweringTransforms(ChirrtlForm, LowForm) ++ Seq(new MinimumLowFirrtlOptimization) } /** Currently just an alias for the [[VerilogCompiler]] */ class SystemVerilogCompiler extends VerilogCompiler { - Driver.dramaticWarning("SystemVerilog Compiler behaves the same as the Verilog Compiler!") + override val emitter = new SystemVerilogEmitter + StageUtils.dramaticWarning("SystemVerilog Compiler behaves the same as the Verilog Compiler!") } diff --git a/src/main/scala/firrtl/annotations/Annotation.scala b/src/main/scala/firrtl/annotations/Annotation.scala index 25e361f3..873bd8fe 100644 --- a/src/main/scala/firrtl/annotations/Annotation.scala +++ b/src/main/scala/firrtl/annotations/Annotation.scala @@ -6,6 +6,7 @@ package annotations import net.jcazevedo.moultingyaml._ import firrtl.annotations.AnnotationYamlProtocol._ import firrtl.Utils.throwInternalError +import firrtl.options.StageUtils import scala.collection.mutable @@ -190,7 +191,7 @@ private[firrtl] object LegacyAnnotation { case other => other } // scalastyle:on - def convertLegacyAnnos(annos: Seq[Annotation]): Seq[Annotation] = { + def convertLegacyAnnos(annos: AnnotationSeq): AnnotationSeq = { var warned: Boolean = false annos.map { case legacy: LegacyAnnotation => @@ -198,7 +199,7 @@ private[firrtl] object LegacyAnnotation { if (!warned && (annox ne legacy)) { val msg = s"A LegacyAnnotation was automatically converted.\n" + (" "*9) + "This functionality will soon be removed. Please migrate to new annotations." - Driver.dramaticWarning(msg) + StageUtils.dramaticWarning(msg) warned = true } annox diff --git a/src/main/scala/firrtl/options/Exceptions.scala b/src/main/scala/firrtl/options/Exceptions.scala new file mode 100644 index 00000000..100ff464 --- /dev/null +++ b/src/main/scala/firrtl/options/Exceptions.scala @@ -0,0 +1,20 @@ +// See LICENSE for license details. + +package firrtl.options + +/** Indicate a generic error in a [[Phase]] + * @param message exception message + * @param cause an underlying Exception that this wraps + */ +class PhaseException(val message: String, cause: Throwable = null) extends RuntimeException(message, cause) + +/** Indicate an error related to a bad [[firrtl.annotations.Annotation Annotation]] or it's command line option + * equivalent. This exception is always caught and converted to an error message by a [[Stage]]. Do not use this for + * communicating generic exception information. + */ +class OptionsException(val message: String, cause: Throwable = null) extends IllegalArgumentException(message, cause) + +/** Indicates that a [[Phase]] is missing some mandatory information. This likely occurs either if a user ran something + * out of order or if the compiler did not run things in the correct order. + */ +class PhasePrerequisiteException(message: String, cause: Throwable = null) extends PhaseException(message, cause) diff --git a/src/main/scala/firrtl/options/OptionParser.scala b/src/main/scala/firrtl/options/OptionParser.scala index 6d8095c0..986c5a8a 100644 --- a/src/main/scala/firrtl/options/OptionParser.scala +++ b/src/main/scala/firrtl/options/OptionParser.scala @@ -2,16 +2,28 @@ package firrtl.options -import firrtl.{FIRRTLException, AnnotationSeq} +import firrtl.AnnotationSeq import scopt.OptionParser -/** Causes an OptionParser to not call exit (call `sys.exit`) if the `--help` option is passed - */ +case object OptionsHelpException extends Exception("Usage help invoked") + +/** OptionParser mixin that causes the OptionParser to not call exit (call `sys.exit`) if the `--help` option is + * passed */ trait DoNotTerminateOnExit { this: OptionParser[_] => override def terminate(exitState: Either[String, Unit]): Unit = Unit } +/** OptionParser mixin that converts to [[OptionsException]] + * + * Scopt, by default, will print errors to stderr, e.g., invalid arguments will do this. However, a [[Stage]] uses + * [[StageUtils.dramaticError]]. By converting this to an [[OptionsException]], a [[Stage]] can then catch the error an + * convert it to an [[OptionsException]] that a [[Stage]] can get at. + */ +trait ExceptOnError { this: OptionParser[_] => + override def reportError(msg: String): Unit = throw new OptionsException(msg) +} + /** A modified OptionParser with mutable termination and additional checks */ trait DuplicateHandling extends OptionParser[AnnotationSeq] { diff --git a/src/main/scala/firrtl/options/OptionsView.scala b/src/main/scala/firrtl/options/OptionsView.scala index aacd997e..4235b660 100644 --- a/src/main/scala/firrtl/options/OptionsView.scala +++ b/src/main/scala/firrtl/options/OptionsView.scala @@ -4,26 +4,31 @@ package firrtl.options import firrtl.AnnotationSeq -/** Type class defining a "view" of an [[AnnotationSeq]] - * @tparam T the type to which this viewer converts an [[AnnotationSeq]] to +/** Type class defining a "view" of an [[firrtl.AnnotationSeq AnnotationSeq]] + * @tparam T the type to which this viewer converts an [[firrtl.AnnotationSeq AnnotationSeq]] to */ trait OptionsView[T] { - /** Convert an [[AnnotationSeq]] to some other type + /** Convert an [[firrtl.AnnotationSeq AnnotationSeq]] to some other type * @param options some annotations */ def view(options: AnnotationSeq): T } -/** A shim to manage multiple "views" of an [[AnnotationSeq]] */ +/** A shim to manage multiple "views" of an [[firrtl.AnnotationSeq AnnotationSeq]] */ object Viewer { + /** Helper method to get at a given [[OptionsView]]. This enables access to [[OptionsView]] methods in a more canonical + * format, e.g., you can then do `Viewer[T].view`. + * @param a an implicit [[OptionsView]] + */ + def apply[T](implicit a: OptionsView[T]): OptionsView[T] = a + /** Convert annotations to options using an implicitly provided [[OptionsView]] * @param options some annotations - * @param optionsView a converter of options to the requested type - * @tparam T the type to which the input [[AnnotationSeq]] should be viewed as + * @tparam T the type to which the input [[firrtl.AnnotationSeq AnnotationSeq]] should be viewed as */ - def view[T](options: AnnotationSeq)(implicit optionsView: OptionsView[T]): T = optionsView.view(options) + def view[T: OptionsView](options: AnnotationSeq): T = Viewer[T].view(options) } diff --git a/src/main/scala/firrtl/options/Phase.scala b/src/main/scala/firrtl/options/Phase.scala index 22715bc2..34739053 100644 --- a/src/main/scala/firrtl/options/Phase.scala +++ b/src/main/scala/firrtl/options/Phase.scala @@ -3,18 +3,74 @@ package firrtl.options import firrtl.AnnotationSeq +import firrtl.annotations.DeletedAnnotation -/** A transformation of an [[AnnotationSeq]] +import logger.LazyLogging + +import scala.collection.mutable + +/** A polymorphic mathematical transform + * @tparam A the transformed type + */ +trait TransformLike[A] extends LazyLogging { + + /** An identifier of this [[TransformLike]] that can be used for logging and informational printing */ + def name: String + + /** A mathematical transform on some type + * @param a an input object + * @return an output object of the same type + */ + def transform(a: A): A + +} + +/** A mathematical transformation of an [[AnnotationSeq]]. + * + * A [[Phase]] forms one unit in the Chisel/FIRRTL Hardware Compiler Framework (HCF). The HCF is built from a sequence + * of [[Phase]]s applied to an [[AnnotationSeq]]. Note that a [[Phase]] may consist of multiple phases internally. + */ +abstract class Phase extends TransformLike[AnnotationSeq] { + + /** The name of this [[Phase]]. This will be used to generate debug/error messages or when deleting annotations. This + * will default to the `simpleName` of the class. + * @return this phase's name + * @note Override this with your own implementation for different naming behavior. + */ + lazy val name: String = this.getClass.getName + +} + +/** A [[TransformLike]] that internally ''translates'' the input type to some other type, transforms the internal type, + * and converts back to the original type. * - * A [[Phase]] forms one block in the Chisel/FIRRTL Hardware Compiler Framework (HCF). Note that a [[Phase]] may - * consist of multiple phases internally. + * This is intended to be used to insert a [[TransformLike]] parameterized by type `B` into a sequence of + * [[TransformLike]]s parameterized by type `A`. + * @tparam A the type of the [[TransformLike]] + * @tparam B the internal type */ -abstract class Phase { +trait Translator[A, B] { this: TransformLike[A] => + + /** A method converting type `A` into type `B` + * @param an object of type `A` + * @return an object of type `B` + */ + protected implicit def aToB(a: A): B + + /** A method converting type `B` back into type `A` + * @param an object of type `B` + * @return an object of type `A` + */ + protected implicit def bToA(b: B): A + + /** A transform on an internal type + * @param b an object of type `B` + * @return an object of type `B` + */ + protected def internalTransform(b: B): B - /** A transformation of an [[AnnotationSeq]] - * @param annotations some annotations - * @return transformed annotations + /** Convert the input object to the internal type, transform the internal type, and convert back to the original type */ - def transform(annotations: AnnotationSeq): AnnotationSeq + final def transform(a: A): A = internalTransform(a) } diff --git a/src/main/scala/firrtl/options/Registration.scala b/src/main/scala/firrtl/options/Registration.scala index 481c095b..c832ec7c 100644 --- a/src/main/scala/firrtl/options/Registration.scala +++ b/src/main/scala/firrtl/options/Registration.scala @@ -4,33 +4,70 @@ package firrtl.options import firrtl.{AnnotationSeq, Transform} -import scopt.OptionParser +import scopt.{OptionDef, OptionParser, Read} + +/** Contains information about a [[Shell]] command line option + * @tparam the type of the command line argument + * @param longOption a long, double-dash option + * @param toAnnotationSeq a function to convert the type into an [[firrtl.AnnotationSeq AnnotationSeq]] + * @param helpText help text + * @param shortOption an optional single-dash option + * @param helpValueName a string to show as a placeholder argument in help text + */ +final class ShellOption[A: Read] ( + val longOption: String, + val toAnnotationSeq: A => AnnotationSeq, + val helpText: String, + val shortOption: Option[String] = None, + val helpValueName: Option[String] = None +) { + + /** Add this specific shell (command line) option to an option parser + * @param p an option parser + */ + final def addOption(p: OptionParser[AnnotationSeq]): Unit = { + val f = Seq( + (p: OptionDef[A, AnnotationSeq]) => p.action( (x, c) => toAnnotationSeq(x).reverse ++ c ), + (p: OptionDef[A, AnnotationSeq]) => p.text(helpText), + (p: OptionDef[A, AnnotationSeq]) => p.unbounded()) ++ + shortOption.map( a => (p: OptionDef[A, AnnotationSeq]) => p.abbr(a) ) ++ + helpValueName.map( a => (p: OptionDef[A, AnnotationSeq]) => p.valueName(a) ) + + f.foldLeft(p.opt[A](longOption))( (a, b) => b(a) ) + } +} /** Indicates that this class/object includes options (but does not add these as a registered class) */ -trait HasScoptOptions { +trait HasShellOptions { + + /** A sequence of options provided + */ + def options: Seq[ShellOption[_]] - /** This method will be called to add options to an OptionParser + /** Add all shell (command line) options to an option parser * @param p an option parser */ - def addOptions(p: OptionParser[AnnotationSeq]): Unit + final def addOptions(p: OptionParser[AnnotationSeq]): Unit = options.foreach(_.addOption(p)) + } -/** A [[Transform]] that includes options that should be exposed at the top level. +/** A [[Transform]] that includes an option that should be exposed at the top level. * * @note To complete registration, include an entry in * src/main/resources/META-INF/services/firrtl.options.RegisteredTransform */ -trait RegisteredTransform extends HasScoptOptions { this: Transform => } +trait RegisteredTransform extends HasShellOptions { this: Transform => } /** A class that includes options that should be exposed as a group at the top level. * * @note To complete registration, include an entry in * src/main/resources/META-INF/services/firrtl.options.RegisteredLibrary */ -trait RegisteredLibrary extends HasScoptOptions { +trait RegisteredLibrary extends HasShellOptions { /** The name of this library. * * This will be used when generating help text. */ def name: String + } diff --git a/src/main/scala/firrtl/options/Shell.scala b/src/main/scala/firrtl/options/Shell.scala index 4fb89450..28c0554a 100644 --- a/src/main/scala/firrtl/options/Shell.scala +++ b/src/main/scala/firrtl/options/Shell.scala @@ -4,25 +4,22 @@ package firrtl.options import firrtl.AnnotationSeq +import logger.{LogLevelAnnotation, ClassLogLevelAnnotation, LogFileAnnotation, LogClassNamesAnnotation} + import scopt.OptionParser import java.util.ServiceLoader -/** Indicate an error in [[firrtl.options]] - * @param msg a message to print - */ -case class OptionsException(msg: String, cause: Throwable = null) extends Exception(msg, cause) - /** A utility for working with command line options * @param applicationName the application associated with these command line options */ class Shell(val applicationName: String) { /** Command line argument parser (OptionParser) with modifications */ - final val parser = new OptionParser[AnnotationSeq](applicationName) with DuplicateHandling + protected val parser = new OptionParser[AnnotationSeq](applicationName) with DuplicateHandling with ExceptOnError /** Contains all discovered [[RegisteredLibrary]] */ - lazy val registeredLibraries: Seq[RegisteredLibrary] = { + final lazy val registeredLibraries: Seq[RegisteredLibrary] = { val libraries = scala.collection.mutable.ArrayBuffer[RegisteredLibrary]() val iter = ServiceLoader.load(classOf[RegisteredLibrary]).iterator() while (iter.hasNext) { @@ -31,19 +28,21 @@ class Shell(val applicationName: String) { parser.note(lib.name) lib.addOptions(parser) } + libraries } /** Contains all discovered [[RegisteredTransform]] */ - lazy val registeredTransforms: Seq[RegisteredTransform] = { + final lazy val registeredTransforms: Seq[RegisteredTransform] = { val transforms = scala.collection.mutable.ArrayBuffer[RegisteredTransform]() val iter = ServiceLoader.load(classOf[RegisteredTransform]).iterator() - parser.note("FIRRTL Transform Options") + if (iter.hasNext) { parser.note("FIRRTL Transform Options") } while (iter.hasNext) { val tx = iter.next() transforms += tx tx.addOptions(parser) } + transforms } @@ -56,12 +55,35 @@ class Shell(val applicationName: String) { registeredTransforms registeredLibraries parser - .parse(args, initAnnos) + .parse(args, initAnnos.reverse) .getOrElse(throw new OptionsException("Failed to parse command line options", new IllegalArgumentException)) + .reverse } parser.note("Shell Options") - Seq( InputAnnotationFileAnnotation(), - TargetDirAnnotation() ) - .map(_.addOptions(parser)) + ProgramArgsAnnotation.addOptions(parser) + Seq( TargetDirAnnotation, + InputAnnotationFileAnnotation, + OutputAnnotationFileAnnotation ) + .foreach(_.addOptions(parser)) + + parser.opt[Unit]("show-registrations") + .action{ (_, c) => + val rtString = registeredTransforms.map(r => s"\n - ${r.getClass.getName}").mkString + val rlString = registeredLibraries.map(l => s"\n - ${l.getClass.getName}").mkString + + println(s"""|The following FIRRTL transforms registered command line options:$rtString + |The following libraries registered command line options:$rlString""".stripMargin) + c } + .unbounded() + .text("print discovered registered libraries and transforms") + + parser.help("help").text("prints this usage text") + + parser.note("Logging Options") + Seq( LogLevelAnnotation, + ClassLogLevelAnnotation, + LogFileAnnotation, + LogClassNamesAnnotation ) + .foreach(_.addOptions(parser)) } diff --git a/src/main/scala/firrtl/options/Stage.scala b/src/main/scala/firrtl/options/Stage.scala index 4e74652f..f2780761 100644 --- a/src/main/scala/firrtl/options/Stage.scala +++ b/src/main/scala/firrtl/options/Stage.scala @@ -4,7 +4,7 @@ package firrtl.options import firrtl.AnnotationSeq -case class StageException(val str: String, cause: Throwable = null) extends RuntimeException(str, cause) +import logger.Logger /** A [[Stage]] represents one stage in the FIRRTL hardware compiler framework. A [[Stage]] is, conceptually, a * [[Phase]] that includes a command line interface. @@ -19,37 +19,59 @@ abstract class Stage extends Phase { /** A utility that helps convert command line options to annotations */ val shell: Shell - /** Run this [[Stage]] on some input annotations + /** Run this stage on some input annotations * @param annotations input annotations * @return output annotations */ def run(annotations: AnnotationSeq): AnnotationSeq - /** Execute this [[Stage]] on some input annotations. Annotations will be read from any input annotation files. + /** Execute this stage on some input annotations. Annotations will be read from any input annotation files. * @param annotations input annotations * @return output annotations + * @throws OptionsException if command line or annotation validation fails */ final def transform(annotations: AnnotationSeq): AnnotationSeq = { - val preprocessing: Seq[Phase] = Seq( - phases.GetIncludes, - phases.ConvertLegacyAnnotations, - phases.AddDefaults ) + val annotationsx = + Seq( new phases.GetIncludes, + new phases.ConvertLegacyAnnotations ) + .map(phases.DeletedWrapper(_)) + .foldLeft(annotations)((a, p) => p.transform(a)) - val a = preprocessing.foldLeft(annotations)((a, p) => p.transform(a)) - - run(a) + Logger.makeScope(annotationsx) { + Seq( new phases.AddDefaults, + new phases.Checks, + new Phase { def transform(a: AnnotationSeq) = run(a) }, + new phases.WriteOutputAnnotations ) + .map(phases.DeletedWrapper(_)) + .foldLeft(annotationsx)((a, p) => p.transform(a)) + } } - /** Run this [[Stage]] on on a mix of arguments and annotations + /** Run this stage on on a mix of arguments and annotations * @param args command line arguments * @param initialAnnotations annotation * @return output annotations + * @throws OptionsException if command line or annotation validation fails */ final def execute(args: Array[String], annotations: AnnotationSeq): AnnotationSeq = transform(shell.parse(args, annotations)) - /** The main function that serves as this [[Stage]]'s command line interface +} + +/** Provides a main method for a [[Stage]] + * @param stage the stage to run + */ +class StageMain(val stage: Stage) { + + /** The main function that serves as this stage's command line interface. * @param args command line arguments */ - final def main(args: Array[String]): Unit = execute(args, Seq.empty) + final def main(args: Array[String]): Unit = try { + stage.execute(args, Seq.empty) + } catch { + case a: OptionsException => + StageUtils.dramaticUsageError(a.message) + System.exit(1) + } + } diff --git a/src/main/scala/firrtl/options/StageAnnotations.scala b/src/main/scala/firrtl/options/StageAnnotations.scala index fdad07c3..e35a6afa 100644 --- a/src/main/scala/firrtl/options/StageAnnotations.scala +++ b/src/main/scala/firrtl/options/StageAnnotations.scala @@ -3,40 +3,99 @@ package firrtl.options import firrtl.AnnotationSeq -import firrtl.annotations.NoTargetAnnotation +import firrtl.annotations.{Annotation, NoTargetAnnotation} import scopt.OptionParser -sealed trait StageOption extends HasScoptOptions +sealed trait StageOption { this: Annotation => } + +/** An annotation that should not be serialized automatically [[phases.WriteOutputAnnotations WriteOutputAnnotations]]. + * This usually means that this is an annotation that is used only internally to a [[Stage]]. + */ +trait Unserializable { this: Annotation => } + +/** Holds the name of the target directory + * - set with `-td/--target-dir` + * - if unset, a [[TargetDirAnnotation]] will be generated with the + * @param value target directory name + */ +case class TargetDirAnnotation(directory: String = ".") extends NoTargetAnnotation with StageOption + +object TargetDirAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "target-dir", + toAnnotationSeq = (a: String) => Seq(TargetDirAnnotation(a)), + helpText = "Work directory (default: '.')", + shortOption = Some("td"), + helpValueName = Some("<directory>") ) ) + +} + +/** Additional arguments + * - set with any trailing option on the command line + * @param value one [[scala.Predef.String String]] argument + */ +case class ProgramArgsAnnotation(arg: String) extends NoTargetAnnotation with StageOption + +object ProgramArgsAnnotation { + + def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.arg[String]("<arg>...") + .unbounded() + .optional() + .action( (x, c) => ProgramArgsAnnotation(x) +: c ) + .text("optional unbounded args") +} /** Holds a filename containing one or more [[annotations.Annotation]] to be read * - this is not stored in [[FirrtlExecutionOptions]] * - set with `-faf/--annotation-file` * @param value input annotation filename */ -case class InputAnnotationFileAnnotation(value: String) extends NoTargetAnnotation with StageOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("annotation-file") - .abbr("faf") - .unbounded() - .valueName("<input-anno-file>") - .action( (x, c) => c :+ InputAnnotationFileAnnotation(x) ) - .text("Used to specify annotation file") +case class InputAnnotationFileAnnotation(file: String) extends NoTargetAnnotation with StageOption + +object InputAnnotationFileAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "annotation-file", + toAnnotationSeq = (a: String) => Seq(InputAnnotationFileAnnotation(a)), + helpText = "An input annotation file", + shortOption = Some("faf"), + helpValueName = Some("<file>") ) ) + } -object InputAnnotationFileAnnotation { - private [firrtl] def apply(): InputAnnotationFileAnnotation = InputAnnotationFileAnnotation("") +/** An explicit output _annotation_ file to write to + * - set with `-foaf/--output-annotation-file` + * @param value output annotation filename + */ +case class OutputAnnotationFileAnnotation(file: String) extends NoTargetAnnotation with StageOption + +object OutputAnnotationFileAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "output-annotation-file", + toAnnotationSeq = (a: String) => Seq(OutputAnnotationFileAnnotation(a)), + helpText = "An output annotation file", + shortOption = Some("foaf"), + helpValueName = Some("<file>") ) ) + } -/** Holds the name of the target directory - * - set with `-td/--target-dir` - * - if unset, a [[TargetDirAnnotation]] will be generated with the - * @param value target directory name +/** If this [[firrtl.annotations.Annotation Annotation]] exists in an [[firrtl.AnnotationSeq AnnotationSeq]], then a + * [[firrtl.options.phase.WriteOutputAnnotations WriteOutputAnnotations]] will include + * [[firrtl.annotations.DeletedAnnotation DeletedAnnotation]]s when it writes to a file. + * - set with '--write-deleted' */ -case class TargetDirAnnotation(dir: String = ".") extends NoTargetAnnotation with StageOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("target-dir") - .abbr("td") - .valueName("<target-directory>") - .action( (x, c) => c ++ Seq(TargetDirAnnotation(x)) ) - .unbounded() // See [Note 1] - .text(s"Work directory for intermediate files/blackboxes, default is '.' (current directory)") +case object WriteDeletedAnnotation extends NoTargetAnnotation with StageOption with HasShellOptions { + + val options = Seq( + new ShellOption[Unit]( + longOption = "write-deleted", + toAnnotationSeq = (_: Unit) => Seq(WriteDeletedAnnotation), + helpText = "Include deleted annotations in the output annotation file" ) ) + } diff --git a/src/main/scala/firrtl/options/StageOptions.scala b/src/main/scala/firrtl/options/StageOptions.scala index af5ea17b..7905799c 100644 --- a/src/main/scala/firrtl/options/StageOptions.scala +++ b/src/main/scala/firrtl/options/StageOptions.scala @@ -7,10 +7,31 @@ import java.io.File /** Options that every stage shares * @param targetDirName a target (build) directory * @param an input annotation file + * @param programArgs explicit program arguments + * @param outputAnnotationFileName an output annotation filename */ -final case class StageOptions( - targetDir: String = TargetDirAnnotation().dir, - annotationFiles: Seq[String] = Seq.empty ) { +class StageOptions private [firrtl] ( + val targetDir: String = TargetDirAnnotation().directory, + val annotationFilesIn: Seq[String] = Seq.empty, + val annotationFileOut: Option[String] = None, + val programArgs: Seq[String] = Seq.empty, + val writeDeleted: Boolean = false ) { + + private [options] def copy( + targetDir: String = targetDir, + annotationFilesIn: Seq[String] = annotationFilesIn, + annotationFileOut: Option[String] = annotationFileOut, + programArgs: Seq[String] = programArgs, + writeDeleted: Boolean = writeDeleted ): StageOptions = { + + new StageOptions( + targetDir = targetDir, + annotationFilesIn = annotationFilesIn, + annotationFileOut = annotationFileOut, + programArgs = programArgs, + writeDeleted = writeDeleted ) + + } /** Generate a filename (with an optional suffix) and create any parent directories. Suffix is only added if it is not * already there. @@ -18,7 +39,6 @@ final case class StageOptions( * @param suffix an optional suffix that the file must end in * @return the name of the file * @note the filename may include a path - * @throws IllegalArgumentException if the filename is empty or if the suffix doesn't start with a '.' */ def getBuildFileName(filename: String, suffix: Option[String] = None): String = { require(filename.nonEmpty, "requested filename must not be empty") diff --git a/src/main/scala/firrtl/options/StageUtils.scala b/src/main/scala/firrtl/options/StageUtils.scala index ebbbdd03..cf7cc767 100644 --- a/src/main/scala/firrtl/options/StageUtils.scala +++ b/src/main/scala/firrtl/options/StageUtils.scala @@ -27,7 +27,11 @@ object StageUtils { println("-"*78 + Console.RESET) } - // def canonicalFileName(suffix: String, directory: String = TargetDirAnnotation().targetDirName) { - // } + /** Generate a message suggesting that the user look at the usage text. + * @param message the error message + */ + def dramaticUsageError(message: String): Unit = + dramaticError(s"""|$message + |Try --help for more information.""".stripMargin) } diff --git a/src/main/scala/firrtl/options/package.scala b/src/main/scala/firrtl/options/package.scala index a0dcc194..8cf2875b 100644 --- a/src/main/scala/firrtl/options/package.scala +++ b/src/main/scala/firrtl/options/package.scala @@ -7,10 +7,15 @@ package object options { implicit object StageOptionsView extends OptionsView[StageOptions] { def view(options: AnnotationSeq): StageOptions = options .collect { case a: StageOption => a } - .foldLeft(StageOptions())((c, x) => + .foldLeft(new StageOptions())((c, x) => x match { case TargetDirAnnotation(a) => c.copy(targetDir = a) - case InputAnnotationFileAnnotation(a) => c.copy(annotationFiles = a +: c.annotationFiles) + /* Insert input files at the head of the Seq for speed and because order shouldn't matter */ + case InputAnnotationFileAnnotation(a) => c.copy(annotationFilesIn = a +: c.annotationFilesIn) + case OutputAnnotationFileAnnotation(a) => c.copy(annotationFileOut = Some(a)) + /* Do NOT reorder program args. The order may matter. */ + case ProgramArgsAnnotation(a) => c.copy(programArgs = c.programArgs :+ a) + case WriteDeletedAnnotation => c.copy(writeDeleted = true) } ) } diff --git a/src/main/scala/firrtl/options/phases/AddDefaults.scala b/src/main/scala/firrtl/options/phases/AddDefaults.scala index f0749b22..2d4e4e40 100644 --- a/src/main/scala/firrtl/options/phases/AddDefaults.scala +++ b/src/main/scala/firrtl/options/phases/AddDefaults.scala @@ -10,14 +10,10 @@ import firrtl.options.{Phase, StageOption, TargetDirAnnotation} * This currently only adds a [[TargetDirAnnotation]]. This isn't necessary for a [[StageOptionsView]], but downstream * tools may expect a [[TargetDirAnnotation]] to exist. */ -object AddDefaults extends Phase { +class AddDefaults extends Phase { def transform(annotations: AnnotationSeq): AnnotationSeq = { - var td = true - annotations.collect { case a: StageOption => a }.map { - case _: TargetDirAnnotation => td = false - case _ => - } + val td = annotations.collectFirst{ case a: TargetDirAnnotation => a}.isEmpty (if (td) Seq(TargetDirAnnotation()) else Seq()) ++ annotations diff --git a/src/main/scala/firrtl/options/phases/Checks.scala b/src/main/scala/firrtl/options/phases/Checks.scala new file mode 100644 index 00000000..0691e9b0 --- /dev/null +++ b/src/main/scala/firrtl/options/phases/Checks.scala @@ -0,0 +1,43 @@ +// See LICENSE for license details. + +package firrtl.options.phases + +import firrtl.AnnotationSeq +import firrtl.annotations.Annotation +import firrtl.options.{OptionsException, OutputAnnotationFileAnnotation, Phase, TargetDirAnnotation} + +/** [[firrtl.options.Phase Phase]] that validates an [[AnnotationSeq]]. If successful, views of this [[AnnotationSeq]] + * as [[StageOptions]] are guaranteed to succeed. + */ +class Checks extends Phase { + + /** Validate an [[AnnotationSeq]] for [[StageOptions]] + * @throws OptionsException if annotations are invalid + */ + def transform(annotations: AnnotationSeq): AnnotationSeq = { + + val td, outA = collection.mutable.ListBuffer[Annotation]() + annotations.foreach { + case a: TargetDirAnnotation => td += a + case a: OutputAnnotationFileAnnotation => outA += a + case _ => + } + + if (td.size != 1) { + val d = td.map{ case TargetDirAnnotation(x) => x } + throw new OptionsException( + s"""|Exactly one target directory must be specified, but found `${d.mkString(", ")}` specified via: + | - explicit target directory: -td, --target-dir, TargetDirAnnotation + | - fallback default value""".stripMargin )} + + if (outA.size > 1) { + val x = outA.map{ case OutputAnnotationFileAnnotation(x) => x } + throw new OptionsException( + s"""|At most one output annotation file can be specified, but found '${x.mkString(", ")}' specified via: + | - an option or annotation: -foaf, --output-annotation-file, OutputAnnotationFileAnnotation""" + .stripMargin )} + + annotations + } + +} diff --git a/src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala b/src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala index 7ff05370..39f59572 100644 --- a/src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala +++ b/src/main/scala/firrtl/options/phases/ConvertLegacyAnnotations.scala @@ -7,7 +7,7 @@ import firrtl.annotations.LegacyAnnotation import firrtl.options.Phase /** Convert any [[firrtl.annotations.LegacyAnnotation LegacyAnnotation]]s to non-legacy variants */ -object ConvertLegacyAnnotations extends Phase { +class ConvertLegacyAnnotations extends Phase { def transform(annotations: AnnotationSeq): AnnotationSeq = LegacyAnnotation.convertLegacyAnnos(annotations) diff --git a/src/main/scala/firrtl/options/phases/DeletedWrapper.scala b/src/main/scala/firrtl/options/phases/DeletedWrapper.scala new file mode 100644 index 00000000..0a959f32 --- /dev/null +++ b/src/main/scala/firrtl/options/phases/DeletedWrapper.scala @@ -0,0 +1,43 @@ +// See LICENSE for license details. + +package firrtl.options.phases + +import firrtl.AnnotationSeq +import firrtl.annotations.DeletedAnnotation +import firrtl.options.{Phase, Translator} + +import scala.collection.mutable + +/** Wrap a [[firrtl.options.Phase Phase]] such that any [[firrtl.annotations.Annotation Annotation]] removed by the + * wrapped [[firrtl.options.Phase Phase]] will be added as [[firrtl.annotations.DeletedAnnotation DeletedAnnotation]]s. + * @param p a [[firrtl.options.Phase Phase]] to wrap + */ +class DeletedWrapper(p: Phase) extends Phase with Translator[AnnotationSeq, (AnnotationSeq, AnnotationSeq)] { + + override lazy val name: String = p.name + + def aToB(a: AnnotationSeq): (AnnotationSeq, AnnotationSeq) = (a, a) + + def bToA(b: (AnnotationSeq, AnnotationSeq)): AnnotationSeq = { + + val (in, out) = (mutable.LinkedHashSet() ++ b._1, mutable.LinkedHashSet() ++ b._2) + + (in -- out).map { + case DeletedAnnotation(n, a) => DeletedAnnotation(s"$n+$name", a) + case a => DeletedAnnotation(name, a) + }.toSeq ++ b._2 + + } + + def internalTransform(b: (AnnotationSeq, AnnotationSeq)): (AnnotationSeq, AnnotationSeq) = (b._1, p.transform(b._2)) + +} + +object DeletedWrapper { + + /** Wrap a [[firrtl.options.Phase Phase]] in a [[DeletedWrapper]] + * @param p a [[firrtl.options.Phase Phase]] to wrap + */ + def apply(p: Phase): DeletedWrapper = new DeletedWrapper(p) + +} diff --git a/src/main/scala/firrtl/options/phases/GetIncludes.scala b/src/main/scala/firrtl/options/phases/GetIncludes.scala index 8156dbbf..9e198c61 100644 --- a/src/main/scala/firrtl/options/phases/GetIncludes.scala +++ b/src/main/scala/firrtl/options/phases/GetIncludes.scala @@ -5,7 +5,7 @@ package firrtl.options.phases import net.jcazevedo.moultingyaml._ import firrtl.AnnotationSeq -import firrtl.annotations.{AnnotationFileNotFoundException, DeletedAnnotation, JsonProtocol, LegacyAnnotation} +import firrtl.annotations.{AnnotationFileNotFoundException, JsonProtocol, LegacyAnnotation} import firrtl.annotations.AnnotationYamlProtocol._ import firrtl.options.{InputAnnotationFileAnnotation, Phase, StageUtils} @@ -15,7 +15,7 @@ import scala.collection.mutable import scala.util.{Try, Failure} /** Recursively expand all [[InputAnnotationFileAnnotation]]s in an [[AnnotationSeq]] */ -object GetIncludes extends Phase { +class GetIncludes extends Phase { /** Read all [[annotations.Annotation]] from a file in JSON or YAML format * @param filename a JSON or YAML file of [[annotations.Annotation]] @@ -46,15 +46,14 @@ object GetIncludes extends Phase { */ private def getIncludes(includeGuard: mutable.Set[String] = mutable.Set()) (annos: AnnotationSeq): AnnotationSeq = { - val phaseName = this.getClass.getName annos.flatMap { case a @ InputAnnotationFileAnnotation(value) => if (includeGuard.contains(value)) { - StageUtils.dramaticWarning("Tried to import the same annotation file twice! (Did you include it twice?)") - Seq(DeletedAnnotation(phaseName, a)) + StageUtils.dramaticWarning(s"Annotation file ($value) already included! (Did you include it more than once?)") + None } else { includeGuard += value - DeletedAnnotation(phaseName, a) +: getIncludes(includeGuard)(readAnnotationsFromFile(value)) + getIncludes(includeGuard)(readAnnotationsFromFile(value)) } case x => Seq(x) } diff --git a/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala b/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala new file mode 100644 index 00000000..bb2a8cd6 --- /dev/null +++ b/src/main/scala/firrtl/options/phases/WriteOutputAnnotations.scala @@ -0,0 +1,36 @@ +// See LICENSE for license details. + +package firrtl.options.phases + +import firrtl.AnnotationSeq +import firrtl.annotations.{DeletedAnnotation, JsonProtocol} +import firrtl.options.{Phase, StageOptions, Unserializable, Viewer} + +import java.io.PrintWriter + +/** [[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]]. + */ +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 + } + + sopts.annotationFileOut match { + case None => + case Some(file) => + val pw = new PrintWriter(sopts.getBuildFileName(file, Some(".anno.json"))) + pw.write(JsonProtocol.serialize(serializable)) + pw.close() + } + + annotations + } + +} diff --git a/src/main/scala/firrtl/package.scala b/src/main/scala/firrtl/package.scala index bc23049c..fc07e05d 100644 --- a/src/main/scala/firrtl/package.scala +++ b/src/main/scala/firrtl/package.scala @@ -8,9 +8,9 @@ package object firrtl { implicit def annoSeqToSeq(as: AnnotationSeq): Seq[Annotation] = as.underlying /* Options as annotations compatibility items */ - @deprecated("Use firrtl.stage.TargetDirAnnotation", "3.2") - type TargetDirAnnotation = firrtl.stage.TargetDirAnnotation + @deprecated("Use firrtl.stage.TargetDirAnnotation", "1.2") + type TargetDirAnnotation = firrtl.options.TargetDirAnnotation - @deprecated("Use firrtl.stage.TargetDirAnnotation", "3.2") - val TargetDirAnnotation = firrtl.stage.TargetDirAnnotation + @deprecated("Use firrtl.stage.TargetDirAnnotation", "1.2") + val TargetDirAnnotation = firrtl.options.TargetDirAnnotation } diff --git a/src/main/scala/firrtl/passes/Inline.scala b/src/main/scala/firrtl/passes/Inline.scala index dcee0ee2..0806563c 100644 --- a/src/main/scala/firrtl/passes/Inline.scala +++ b/src/main/scala/firrtl/passes/Inline.scala @@ -8,7 +8,7 @@ import firrtl.Mappers._ import firrtl.annotations._ import firrtl.analyses.InstanceGraph import firrtl.stage.RunFirrtlTransformAnnotation -import firrtl.options.RegisteredTransform +import firrtl.options.{RegisteredTransform, ShellOption} import scopt.OptionParser // Datastructures @@ -28,24 +28,22 @@ class InlineInstances extends Transform with RegisteredTransform { def outputForm = LowForm private [firrtl] val inlineDelim: String = "_" - def addOptions(parser: OptionParser[AnnotationSeq]): Unit = parser - .opt[Seq[String]]("inline") - .abbr("fil") - .valueName ("<circuit>[.<module>[.<instance>]][,..],") - .action( (x, c) => { - val newAnnotations = x.map { value => - value.split('.') match { - case Array(circuit) => - InlineAnnotation(CircuitName(circuit)) - case Array(circuit, module) => - InlineAnnotation(ModuleName(module, CircuitName(circuit))) - case Array(circuit, module, inst) => - InlineAnnotation(ComponentName(inst, ModuleName(module, CircuitName(circuit)))) - } - } - c ++ newAnnotations :+ RunFirrtlTransformAnnotation(new InlineInstances) } ) - .text( - """Inline one or more module (comma separated, no spaces) module looks like "MyModule" or "MyModule.myinstance""") + val options = Seq( + new ShellOption[Seq[String]]( + longOption = "inline", + toAnnotationSeq = (a: Seq[String]) => a.map { value => + value.split('.') match { + case Array(circuit) => + InlineAnnotation(CircuitName(circuit)) + case Array(circuit, module) => + InlineAnnotation(ModuleName(module, CircuitName(circuit))) + case Array(circuit, module, inst) => + InlineAnnotation(ComponentName(inst, ModuleName(module, CircuitName(circuit)))) + } + } :+ RunFirrtlTransformAnnotation(new InlineInstances), + helpText = "Inline selected modules", + shortOption = Some("fil"), + helpValueName = Some("<circuit>[.<module>[.<instance>]][,...]") ) ) private def collectAnns(circuit: Circuit, anns: Iterable[Annotation]): (Set[ModuleName], Set[ComponentName]) = anns.foldLeft(Set.empty[ModuleName], Set.empty[ComponentName]) { diff --git a/src/main/scala/firrtl/passes/clocklist/ClockListTransform.scala b/src/main/scala/firrtl/passes/clocklist/ClockListTransform.scala index de9f6c52..f95787bd 100644 --- a/src/main/scala/firrtl/passes/clocklist/ClockListTransform.scala +++ b/src/main/scala/firrtl/passes/clocklist/ClockListTransform.scala @@ -13,7 +13,7 @@ import ClockListUtils._ import Utils._ import memlib.AnalysisUtils._ import memlib._ -import firrtl.options.RegisteredTransform +import firrtl.options.{RegisteredTransform, ShellOption} import scopt.OptionParser import firrtl.stage.RunFirrtlTransformAnnotation @@ -60,14 +60,14 @@ class ClockListTransform extends Transform with RegisteredTransform { def inputForm = LowForm def outputForm = LowForm - def addOptions(parser: OptionParser[AnnotationSeq]): Unit = parser - .opt[String]("list-clocks") - .abbr("clks") - .valueName ("-c:<circuit>:-m:<module>:-o:<filename>") - .action( (x, c) => c ++ Seq(passes.clocklist.ClockListAnnotation.parse(x), - RunFirrtlTransformAnnotation(new ClockListTransform)) ) - .maxOccurs(1) - .text("List which signal drives each clock of every descendent of specified module") + val options = Seq( + new ShellOption[String]( + longOption = "list-clocks", + toAnnotationSeq = (a: String) => Seq( passes.clocklist.ClockListAnnotation.parse(a), + RunFirrtlTransformAnnotation(new ClockListTransform) ), + helpText = "List which signal drives each clock of every descendent of specified modules", + shortOption = Some("clks"), + helpValueName = Some("-c:<circuit>:-m:<module>:-o:<filename>") ) ) def passSeq(top: String, writer: Writer): Seq[Pass] = Seq(new ClockList(top, writer)) diff --git a/src/main/scala/firrtl/passes/memlib/InferReadWrite.scala b/src/main/scala/firrtl/passes/memlib/InferReadWrite.scala index 3494de45..0602e4f1 100644 --- a/src/main/scala/firrtl/passes/memlib/InferReadWrite.scala +++ b/src/main/scala/firrtl/passes/memlib/InferReadWrite.scala @@ -8,7 +8,7 @@ import firrtl.ir._ import firrtl.Mappers._ import firrtl.PrimOps._ import firrtl.Utils.{one, zero, BoolType} -import firrtl.options.HasScoptOptions +import firrtl.options.{HasShellOptions, ShellOption} import MemPortUtils.memPortField import firrtl.passes.memlib.AnalysisUtils.{Connects, getConnects, getOrigin} import WrappedExpression.weq @@ -147,17 +147,16 @@ object InferReadWritePass extends Pass { // Transform input: Middle Firrtl. Called after "HighFirrtlToMidleFirrtl" // To use this transform, circuit name should be annotated with its TransId. -class InferReadWrite extends Transform with SeqTransformBased with HasScoptOptions { +class InferReadWrite extends Transform with SeqTransformBased with HasShellOptions { def inputForm = MidForm def outputForm = MidForm - def addOptions(parser: OptionParser[AnnotationSeq]): Unit = parser - .opt[Unit]("infer-rw") - .abbr("firw") - .valueName ("<circuit>") - .action( (_, c) => c ++ Seq(InferReadWriteAnnotation, RunFirrtlTransformAnnotation(new InferReadWrite)) ) - .maxOccurs(1) - .text("Enable readwrite port inference for the target circuit") + val options = Seq( + new ShellOption[Unit]( + longOption = "infer-rw", + toAnnotationSeq = (_: Unit) => Seq(InferReadWriteAnnotation, RunFirrtlTransformAnnotation(new InferReadWrite)), + helpText = "Enable read/write port inference for memories", + shortOption = Some("firw") ) ) def transforms = Seq( InferReadWritePass, diff --git a/src/main/scala/firrtl/passes/memlib/MemLibOptions.scala b/src/main/scala/firrtl/passes/memlib/MemLibOptions.scala index 2f26e4e5..4076d5d6 100644 --- a/src/main/scala/firrtl/passes/memlib/MemLibOptions.scala +++ b/src/main/scala/firrtl/passes/memlib/MemLibOptions.scala @@ -3,13 +3,14 @@ package firrtl.passes.memlib import firrtl._ -import firrtl.options.RegisteredLibrary +import firrtl.options.{RegisteredLibrary, ShellOption} import scopt.OptionParser class MemLibOptions extends RegisteredLibrary { val name: String = "MemLib Options" - def addOptions(p: OptionParser[AnnotationSeq]): Unit = - Seq( new InferReadWrite, - new ReplSeqMem ) - .map(_.addOptions(p)) + + val options: Seq[ShellOption[_]] = Seq( new InferReadWrite, + new ReplSeqMem ) + .flatMap(_.options) + } diff --git a/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala b/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala index 1f8e89be..a9d0cc7c 100644 --- a/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala +++ b/src/main/scala/firrtl/passes/memlib/ReplaceMemTransform.scala @@ -6,7 +6,7 @@ package memlib import firrtl._ import firrtl.ir._ import firrtl.annotations._ -import firrtl.options.HasScoptOptions +import firrtl.options.{HasShellOptions, ShellOption} import AnalysisUtils._ import Utils.error import java.io.{File, CharArrayWriter, PrintWriter} @@ -102,18 +102,18 @@ class SimpleTransform(p: Pass, form: CircuitForm) extends Transform { class SimpleMidTransform(p: Pass) extends SimpleTransform(p, MidForm) // SimpleRun instead of PassBased because of the arguments to passSeq -class ReplSeqMem extends Transform with HasScoptOptions { +class ReplSeqMem extends Transform with HasShellOptions { def inputForm = MidForm def outputForm = MidForm - def addOptions(parser: OptionParser[AnnotationSeq]): Unit = parser - .opt[String]("repl-seq-mem") - .abbr("frsq") - .valueName ("-c:<circuit>:-i:<filename>:-o:<filename>") - .action( (x, c) => c ++ Seq(passes.memlib.ReplSeqMemAnnotation.parse(x), - RunFirrtlTransformAnnotation(new ReplSeqMem)) ) - .maxOccurs(1) - .text("Replace sequential memories with blackboxes + configuration file") + val options = Seq( + new ShellOption[String]( + longOption = "repl-seq-mem", + toAnnotationSeq = (a: String) => Seq( passes.memlib.ReplSeqMemAnnotation.parse(a), + RunFirrtlTransformAnnotation(new ReplSeqMem) ), + helpText = "Blackbox and emit a configuration file for each sequential memory", + shortOption = Some("frsq"), + helpValueName = Some("-c:<circuit>:-i:<file>:-o:<file>") ) ) def transforms(inConfigFile: Option[YamlFileReader], outConfigFile: ConfWriter): Seq[Transform] = Seq(new SimpleMidTransform(Legalize), diff --git a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala index c88591fb..ed5415f6 100644 --- a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala +++ b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala @@ -4,287 +4,208 @@ package firrtl.stage import firrtl._ import firrtl.ir.Circuit -import firrtl.annotations.NoTargetAnnotation -import firrtl.transforms.BlackBoxTargetDirAnno -import firrtl.options.HasScoptOptions - -import logger.LogLevel +import firrtl.annotations.{Annotation, NoTargetAnnotation} +import firrtl.options.{HasShellOptions, OptionsException, ShellOption} import scopt.OptionParser -/** Indicates that a subclass is an [[firrtl.annotations Annotation]] that includes a command line option - * - * This must be mixed into a subclass of [[annotations.Annotation]] - */ -sealed trait FirrtlOption extends HasScoptOptions - -/** Holds the name of the top module - * - set on the command line with `-tn/--top-name` - * @param value top module name - */ -case class TopNameAnnotation(topName: String) extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("top-name") - .abbr("tn") - .valueName("<top-level-circuit-name>") - .action( (x, c) => c :+ TopNameAnnotation(x) ) - .unbounded() // See [Note 1] - .text("This options defines the top level circuit, defaults to dut when possible") -} - -object TopNameAnnotation { - private [firrtl] def apply(): TopNameAnnotation = TopNameAnnotation(topName = "") -} +import java.io.FileNotFoundException +import java.nio.file.NoSuchFileException -/** Holds the name of the target directory - * - set with `-td/--target-dir` - * - if unset, a [[TargetDirAnnotation]] will be generated with the - * @param value target directory name +/** Indicates that this is an [[firrtl.annotations.Annotation Annotation]] directly used in the construction of a + * [[FirrtlOptions]] view. */ -case class TargetDirAnnotation(targetDirName: String = ".") extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("target-dir") - .abbr("td") - .valueName("<target-directory>") - .action( (x, c) => c ++ Seq(TargetDirAnnotation(x)) ) - .unbounded() // See [Note 1] - .text(s"Work directory for intermediate files/blackboxes, default is ${CommonOptions().targetDirName}") -} +sealed trait FirrtlOption { this: Annotation => } -/** Describes the verbosity of information to log - * - set with `-ll/--log-level` - * - if unset, a [[LogLevelAnnotation]] with the default log level will be emitted - * @param level the level of logging +/** Indicates that this [[firrtl.annotations.Annotation Annotation]] contains information that is directly convertable + * to a FIRRTL [[firrtl.ir.Circuit Circuit]]. */ -case class LogLevelAnnotation(globalLogLevel: LogLevel.Value = LogLevel.None) extends NoTargetAnnotation with FirrtlOption { - val value = globalLogLevel.toString - - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("log-level") - .abbr("ll") - .valueName("<Error|Warn|Info|Debug|Trace>") - .action( (x, c) => c :+ LogLevelAnnotation(LogLevel(x)) ) - .validate{ x => - lazy val msg = s"$x bad value must be one of error|warn|info|debug|trace" - if (Array("error", "warn", "info", "debug", "trace").contains(x.toLowerCase)) { p.success } - else { p.failure(msg) }} - .unbounded() // See [Note 1] - .text(s"Sets the verbosity level of logging, default is ${CommonOptions().globalLogLevel}") -} +sealed trait CircuitOption extends { this: Annotation => -/** Describes a mapping of a class to a specific log level - * - set with `-cll/--class-log-level` - * @param name the class name to log - * @param level the verbosity level - */ -case class ClassLogLevelAnnotation(className: String, level: LogLevel.Value) extends NoTargetAnnotation - with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[Seq[String]]("class-log-level") - .abbr("cll") - .valueName("<FullClassName:[Error|Warn|Info|Debug|Trace]>[,...]") - .action( (x, c) => c ++ (x.map { y => - val className :: levelName :: _ = y.split(":").toList - val level = LogLevel(levelName) - ClassLogLevelAnnotation(className, level) }) ) - .unbounded() // This can actually occur any number of times safely - .text(s"This defines per-class verbosity of logging") -} + /** Convert this [[firrtl.annotations.Annotation Annotation]] to a [[FirrtlCircuitAnnotation]] + */ + def toCircuit(info: Parser.InfoMode): FirrtlCircuitAnnotation -object ClassLogLevelAnnotation { - private [firrtl] def apply(): ClassLogLevelAnnotation = ClassLogLevelAnnotation("", LogLevel.None) } -/** Enables logging to a file (as opposed to STDOUT) - * - enabled with `-ltf/--log-to-file` +/** An explicit input FIRRTL file to read + * - set with `-i/--input-file` + * - If unset, an [[FirrtlFileAnnotation]] with the default input file __will not be generated__ + * @param file input filename */ -case object LogToFileAnnotation extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[Unit]("log-to-file") - .abbr("ltf") - .action( (x, c) => c :+ LogToFileAnnotation ) - .unbounded() - .text(s"default logs to stdout, this flags writes to topName.log or firrtl.log if no topName") -} +case class FirrtlFileAnnotation(file: String) extends NoTargetAnnotation with CircuitOption { -/** Enables class names in log output - * - enabled with `-lcn/--log-class-names` - */ -case object LogClassNamesAnnotation extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[Unit]("log-class-names") - .abbr("lcn") - .action( (x, c) => c :+ LogClassNamesAnnotation ) - .unbounded() - .text(s"shows class names and log level in logging output, useful for target --class-log-level") -} + def toCircuit(info: Parser.InfoMode): FirrtlCircuitAnnotation = { + val circuit = try { + FirrtlStageUtils.getFileExtension(file) match { + case ProtoBufFile => proto.FromProto.fromFile(file) + case FirrtlFile => Parser.parseFile(file, info) } + } catch { + case a @ (_: FileNotFoundException | _: NoSuchFileException) => + throw new OptionsException(s"Input file '$file' not found! (Did you misspell it?)", a) + } + FirrtlCircuitAnnotation(circuit) + } -/** Additional arguments - * - set with any trailing option on the command line - * @param value one [[scala.Predef.String String]] argument - */ -case class ProgramArgsAnnotation(value: String) extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.arg[String]("<arg>...") - .unbounded() - .optional() - .action( (x, c) => c :+ ProgramArgsAnnotation(x) ) - .text("optional unbounded args") } -object ProgramArgsAnnotation { - private [firrtl] def apply(): ProgramArgsAnnotation = ProgramArgsAnnotation("") -} +object FirrtlFileAnnotation extends HasShellOptions { -/** An explicit input FIRRTL file to read - * - set with `-i/--input-file` - * - If unset, an [[InputFileAnnotation]] with the default input file __will not be generated__ - * @param value input filename - */ -case class InputFileAnnotation(value: String) extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("input-file") - .abbr("i") - .valueName ("<firrtl-source>") - .action( (x, c) => c :+ InputFileAnnotation(x) ) - .unbounded() // See [Note 1] - .text("use this to override the default input file name, default is empty") -} + val options = Seq( + new ShellOption[String]( + longOption = "input-file", + toAnnotationSeq = a => Seq(FirrtlFileAnnotation(a)), + helpText = "An input FIRRTL file", + shortOption = Some("i"), + helpValueName = Some("<file>") ) ) -object InputFileAnnotation { - private [firrtl] def apply(): InputFileAnnotation = InputFileAnnotation("") } /** An explicit output file the emitter will write to * - set with `-o/--output-file` - * @param value output filename + * @param file output filename */ -case class OutputFileAnnotation(value: String) extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("output-file") - .abbr("o") - .valueName("<output>") - .action( (x, c) => c :+ OutputFileAnnotation(x) ) - .unbounded() - .text("use this to override the default output file name, default is empty") -} +case class OutputFileAnnotation(file: String) extends NoTargetAnnotation with FirrtlOption -object OutputFileAnnotation { - private [firrtl] def apply(): OutputFileAnnotation = OutputFileAnnotation("") -} +object OutputFileAnnotation extends HasShellOptions { -/** An explicit output _annotation_ file to write to - * - set with `-foaf/--output-annotation-file` - * @param value output annotation filename - */ -case class OutputAnnotationFileAnnotation(value: String) extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("output-annotation-file") - .abbr("foaf") - .valueName ("<output-anno-file>") - .action( (x, c) => c :+ OutputAnnotationFileAnnotation(x) ) - .unbounded() // See [Note 1] - .text("use this to set the annotation output file") -} + val options = Seq( + new ShellOption[String]( + longOption = "output-file", + toAnnotationSeq = a => Seq(OutputFileAnnotation(a)), + helpText = "The output FIRRTL file", + shortOption = Some("o"), + helpValueName = Some("<file>") ) ) -object OutputAnnotationFileAnnotation { - private [firrtl] def apply(): OutputAnnotationFileAnnotation = OutputAnnotationFileAnnotation("") } /** Sets the info mode style * - set with `--info-mode` - * @param value info mode name + * @param mode info mode name + * @note This cannote be directly converted to [[Parser.InfoMode]] as that depends on an optional [[FirrtlFileAnnotation]] */ -case class InfoModeAnnotation(value: String = "append") extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("info-mode") - .valueName ("<ignore|use|gen|append>") - .action( (x, c) => c :+ InfoModeAnnotation(x.toLowerCase) ) - .unbounded() // See [Note 1] - .text(s"specifies the source info handling, default is ${FirrtlExecutionOptions().infoModeName}") +case class InfoModeAnnotation(modeName: String = "use") extends NoTargetAnnotation with FirrtlOption { + require(modeName match { case "use" | "ignore" | "gen" | "append" => true; case _ => false }, + s"Unknown info mode '$modeName'! (Did you misspell it?)") + + /** Return the [[Parser.InfoMode]] equivalent for this [[firrtl.annotations.Annotation Annotation]] + * @param infoSource the name of a file to use for "gen" or "append" info modes + */ + def toInfoMode(infoSource: Option[String] = None): Parser.InfoMode = modeName match { + case "use" => Parser.UseInfo + case "ignore" => Parser.IgnoreInfo + case _ => + val a = infoSource.getOrElse("unknown source") + modeName match { + case "gen" => Parser.GenInfo(a) + case "append" => Parser.AppendInfo(a) + } + } +} + +object InfoModeAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "info-mode", + toAnnotationSeq = a => Seq(InfoModeAnnotation(a)), + helpText = s"Source file info handling mode (default: ${apply().modeName})", + shortOption = Some("<ignore|use|gen|append>") ) ) + } /** Holds a [[scala.Predef.String String]] containing FIRRTL source to read as input * - set with `--firrtl-source` * @param value FIRRTL source as a [[scala.Predef.String String]] */ -case class FirrtlSourceAnnotation(value: String) extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("firrtl-source") - .valueName ("A FIRRTL string") - .action( (x, c) => c :+ FirrtlSourceAnnotation(x) ) - .unbounded() // See [Note 1] - .text(s"A FIRRTL circuit as a string") -} +case class FirrtlSourceAnnotation(source: String) extends NoTargetAnnotation with CircuitOption { -object FirrtlSourceAnnotation { - private [firrtl] def apply(): FirrtlSourceAnnotation = FirrtlSourceAnnotation("") -} + def toCircuit(info: Parser.InfoMode): FirrtlCircuitAnnotation = + FirrtlCircuitAnnotation(Parser.parseString(source, info)) -/** Indicates that an emitted circuit (FIRRTL, Verilog, etc.) will be one file per module - * - set with `--split-modules` - */ -case object EmitOneFilePerModuleAnnotation extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[Unit]("split-modules") - .abbr("fsm") - .action( (x, c) => c :+ EmitOneFilePerModuleAnnotation ) - .unbounded() - .text ("Emit each module to its own file in the target directory.") } -/** Holds a filename containing one or more [[annotations.Annotation]] to be read - * - this is not stored in [[FirrtlExecutionOptions]] - * - set with `-faf/--annotation-file` - * @param value input annotation filename - */ -case class InputAnnotationFileAnnotation(value: String) extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("annotation-file") - .abbr("faf") - .unbounded() - .valueName("<input-anno-file>") - .action( (x, c) => c :+ InputAnnotationFileAnnotation(x) ) - .text("Used to specify annotation file") -} +object FirrtlSourceAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "firrtl-source", + toAnnotationSeq = a => Seq(FirrtlSourceAnnotation(a)), + helpText = "An input FIRRTL circuit string", + shortOption = Some("<string>") ) ) -object InputAnnotationFileAnnotation { - private [firrtl] def apply(): InputAnnotationFileAnnotation = InputAnnotationFileAnnotation("") } -/** Holds the name of the compiler to run - * - set with `-X/--compiler` - * - If unset, a [[CompilerNameAnnotation]] with the default compiler ("verilog") __will be generated__ - * @param value compiler name +/** helpValueName a [[Compiler]] that should be run + * - set stringly with `-X/--compiler` + * - If unset, a [[CompilerAnnotation]] with the default [[VerilogCompiler]] + * @param compiler compiler name */ -case class CompilerNameAnnotation(value: String = "verilog") extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[String]("compiler") - .abbr("X") - .valueName ("<high|middle|low|verilog|sverilog>") - .action( (x, c) => c :+ CompilerNameAnnotation(x) ) - .unbounded() // See [Note 1] - .text(s"compiler to use, default is 'verilog'") +case class CompilerAnnotation(compiler: Compiler = new VerilogCompiler()) extends NoTargetAnnotation with FirrtlOption + +object CompilerAnnotation extends HasShellOptions { + + private [firrtl] def apply(compilerName: String): CompilerAnnotation = { + val c = compilerName match { + case "none" => new NoneCompiler() + case "high" => new HighFirrtlCompiler() + case "low" => new LowFirrtlCompiler() + case "middle" => new MiddleFirrtlCompiler() + case "verilog" => new VerilogCompiler() + case "mverilog" => new MinimumVerilogCompiler() + case "sverilog" => new SystemVerilogCompiler() + case _ => throw new OptionsException(s"Unknown compiler name '$compilerName'! (Did you misspell it?)") + } + CompilerAnnotation(c) + } + + val options = Seq( + new ShellOption[String]( + longOption = "compiler", + toAnnotationSeq = a => Seq(CompilerAnnotation(a)), + helpText = "The FIRRTL compiler to use (default: verilog)", + shortOption = Some("X"), + helpValueName = Some("<none|high|middle|low|verilog|mverilog|sverilog>") ) ) + } /** Holds the unambiguous class name of a [[Transform]] to run * - will be append to [[FirrtlExecutionOptions.customTransforms]] * - set with `-fct/--custom-transforms` - * @param value the full class name of the transform + * @param transform the full class name of the transform */ -case class RunFirrtlTransformAnnotation(transform: Transform) extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = p.opt[Seq[String]]("custom-transforms") - .abbr("fct") - .valueName ("<package>.<class>") - .validate( x => { - x.map(txName => - try { Class.forName(txName).asInstanceOf[Class[_ <: Transform]].newInstance() } - catch { - case e: ClassNotFoundException => throw new FIRRTLException( - s"Unable to locate custom transform $txName (did you misspell it?)", e) - case e: InstantiationException => throw new FIRRTLException( - s"Unable to create instance of Transform $txName (is this an anonymous class?)", e) - case e: Throwable => throw new FIRRTLException( - s"Unknown error when instantiating class $txName", e) } ) - p.success } ) - .action( (x, c) => c ++ x.map(txName => - RunFirrtlTransformAnnotation(Class.forName(txName).asInstanceOf[Class[_ <: Transform]].newInstance())) ) - .unbounded() - .text("runs these custom transforms during compilation.") -} +case class RunFirrtlTransformAnnotation(transform: Transform) extends NoTargetAnnotation + +object RunFirrtlTransformAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[Seq[String]]( + longOption = "custom-transforms", + toAnnotationSeq = _.map(txName => + try { + val tx = Class.forName(txName).asInstanceOf[Class[_ <: Transform]].newInstance() + RunFirrtlTransformAnnotation(tx) + } catch { + case e: ClassNotFoundException => throw new OptionsException( + s"Unable to locate custom transform $txName (did you misspell it?)", e) + case e: InstantiationException => throw new OptionsException( + s"Unable to create instance of Transform $txName (is this an anonymous class?)", e) + case e: Throwable => throw new OptionsException( + s"Unknown error when instantiating class $txName", e) }), + helpText = "Run these transforms during compilation", + shortOption = Some("fct"), + helpValueName = Some("<package>.<class>") ) ) -object RunFirrtlTransformAnnotation { - private [firrtl] def apply(): RunFirrtlTransformAnnotation = RunFirrtlTransformAnnotation(new firrtl.transforms.VerilogRename) } /** Holds a FIRRTL [[firrtl.ir.Circuit Circuit]] - * @param value a circuit + * @param circuit a circuit */ -case class FirrtlCircuitAnnotation(value: Circuit) extends NoTargetAnnotation with FirrtlOption { - def addOptions(p: OptionParser[AnnotationSeq]): Unit = Unit +case class FirrtlCircuitAnnotation(circuit: Circuit) extends NoTargetAnnotation with FirrtlOption { + /* Caching the hashCode for a large circuit is necessary due to repeated queries, e.g., in + * [[Compiler.propagateAnnotations]]. Not caching the hashCode will cause severe performance degredations for large + * [[Annotations]]. + */ + override lazy val hashCode: Int = circuit.hashCode + } diff --git a/src/main/scala/firrtl/stage/FirrtlCli.scala b/src/main/scala/firrtl/stage/FirrtlCli.scala new file mode 100644 index 00000000..15c9069e --- /dev/null +++ b/src/main/scala/firrtl/stage/FirrtlCli.scala @@ -0,0 +1,25 @@ +// See LICENSE for license details. + +package firrtl.stage + +import firrtl.options.Shell + +/** [[firrtl.options.Shell Shell]] mixin that provides command line options for FIRRTL. This does not include any + * [[firrtl.options.RegisteredLibrary RegisteredLibrary]] or [[firrtl.options.RegisteredTransform RegisteredTransform]] + * as those are automatically loaded by the [[firrtl.options.Stage Stage]] using this [[firrtl.options.Shell Shell]]. + */ +trait FirrtlCli { this: Shell => + parser.note("FIRRTL Compiler Options") + Seq( FirrtlFileAnnotation, + OutputFileAnnotation, + InfoModeAnnotation, + FirrtlSourceAnnotation, + CompilerAnnotation, + RunFirrtlTransformAnnotation, + firrtl.EmitCircuitAnnotation, + firrtl.EmitAllModulesAnnotation ) + .map(_.addOptions(parser)) + + phases.DriverCompatibility.TopNameAnnotation.addOptions(parser) + phases.DriverCompatibility.EmitOneFilePerModuleAnnotation.addOptions(parser) +} diff --git a/src/main/scala/firrtl/stage/FirrtlOptions.scala b/src/main/scala/firrtl/stage/FirrtlOptions.scala new file mode 100644 index 00000000..235f82c0 --- /dev/null +++ b/src/main/scala/firrtl/stage/FirrtlOptions.scala @@ -0,0 +1,32 @@ +// See LICENSE for license details. + +package firrtl.stage + +import firrtl.{Compiler, Transform} +import firrtl.ir.Circuit + +/** Internal options used to control the FIRRTL compiler stage. + * @param outputFileName output file, default: `targetDir/topName.SUFFIX` with `SUFFIX` as determined by the compiler + * @param compiler which compiler to use (default: [[VerilogCompiler]]) + * @param infoModeName the policy for generating [[firrtl.ir Info]] when processing FIRRTL (default: "append") + * @param firrtlCircuit a [[firrtl.ir Circuit]] + */ +class FirrtlOptions private [stage] ( + val outputFileName: Option[String] = None, + val compiler: Compiler = CompilerAnnotation().compiler, + val infoModeName: String = InfoModeAnnotation().modeName, + val firrtlCircuit: Option[Circuit] = None) { + + private [stage] def copy( + outputFileName: Option[String] = outputFileName, + compiler: Compiler = compiler, + infoModeName: String = infoModeName, + firrtlCircuit: Option[Circuit] = firrtlCircuit ): FirrtlOptions = { + + new FirrtlOptions( + outputFileName = outputFileName, + compiler = compiler, + infoModeName = infoModeName, + firrtlCircuit = firrtlCircuit ) + } +} diff --git a/src/main/scala/firrtl/stage/FirrtlStage.scala b/src/main/scala/firrtl/stage/FirrtlStage.scala new file mode 100644 index 00000000..5d121322 --- /dev/null +++ b/src/main/scala/firrtl/stage/FirrtlStage.scala @@ -0,0 +1,40 @@ +// See LICENSE for license details. + +package firrtl.stage + +import firrtl.{AnnotationSeq, CustomTransformException, FIRRTLException, Utils} +import firrtl.options.{Stage, Phase, PhaseException, Shell, OptionsException, StageMain} +import firrtl.options.phases.DeletedWrapper +import firrtl.passes.{PassException, PassExceptions} + +import scala.util.control.ControlThrowable + +import java.io.PrintWriter + +class FirrtlStage extends Stage { + val shell: Shell = new Shell("firrtl") with FirrtlCli + + private val phases: Seq[Phase] = + Seq( new firrtl.stage.phases.AddDefaults, + new firrtl.stage.phases.AddImplicitEmitter, + new firrtl.stage.phases.Checks, + new firrtl.stage.phases.AddCircuit, + new firrtl.stage.phases.AddImplicitOutputFile, + new firrtl.stage.phases.Compiler, + new firrtl.stage.phases.WriteEmitted ) + .map(DeletedWrapper(_)) + + def run(annotations: AnnotationSeq): AnnotationSeq = try { + phases.foldLeft(annotations)((a, f) => f.transform(a)) + } catch { + /* Rethrow the exceptions which are expected or due to the runtime environment (out of memory, stack overflow, etc.). + * Any UNEXPECTED exceptions should be treated as internal errors. */ + case p @ (_: ControlThrowable | _: PassException | _: PassExceptions | _: FIRRTLException | _: OptionsException + | _: PhaseException) => throw p + case CustomTransformException(cause) => throw cause + case e: Exception => Utils.throwInternalError(exception = Some(e)) + } + +} + +object FirrtlMain extends StageMain(new FirrtlStage) diff --git a/src/main/scala/firrtl/stage/FirrtlStageUtils.scala b/src/main/scala/firrtl/stage/FirrtlStageUtils.scala new file mode 100644 index 00000000..e2304a92 --- /dev/null +++ b/src/main/scala/firrtl/stage/FirrtlStageUtils.scala @@ -0,0 +1,17 @@ +// See LICENSE for license details. + +package firrtl.stage + +private [stage] sealed trait FileExtension +private [stage] case object FirrtlFile extends FileExtension +private [stage] case object ProtoBufFile extends FileExtension + +/** Utilities that help with processing FIRRTL options */ +object FirrtlStageUtils { + + private [stage] def getFileExtension(file: String): FileExtension = file.drop(file.lastIndexOf('.')) match { + case ".pb" => ProtoBufFile + case _ => FirrtlFile + } + +} diff --git a/src/main/scala/firrtl/stage/package.scala b/src/main/scala/firrtl/stage/package.scala new file mode 100644 index 00000000..b4dc0b54 --- /dev/null +++ b/src/main/scala/firrtl/stage/package.scala @@ -0,0 +1,64 @@ +// See LICENSE for license details. + +package firrtl + +import firrtl.annotations.DeletedAnnotation +import firrtl.options.{OptionsView, PhasePrerequisiteException, Viewer} +import firrtl.stage.phases.WriteEmitted + +/** The [[stage]] package provides an implementation of the FIRRTL compiler using the [[firrtl.options]] package. This + * primarily consists of: + * - [[FirrtlStage]], the internal and external (command line) interface to the FIRRTL compiler + * - A number of [[options.Phase Phase]]s that support and compartmentalize the individual operations of + * [[FirrtlStage]] + * - [[FirrtlOptions]], a class representing options that are necessary to drive the [[FirrtlStage]] and its + * [[firrtl.options.Phase Phase]]s + * - [[FirrtlOptionsView]], a utility that constructs an [[options.OptionsView OptionsView]] of [[FirrtlOptions]] + * from an [[AnnotationSeq]] + * - [[FirrtlCli]], the command line options that the [[FirrtlStage]] supports + * - [[FirrtlStageUtils]] containing miscellaneous utilities for [[stage]] + */ +package object stage { + implicit object FirrtlOptionsView extends OptionsView[FirrtlOptions] { + + /** + * @todo custom transforms are appended as discovered, can this be prepended safely? + */ + def view(options: AnnotationSeq): FirrtlOptions = options + .collect { case a: FirrtlOption => a } + .foldLeft(new FirrtlOptions()){ (c, x) => + x match { + case OutputFileAnnotation(f) => c.copy(outputFileName = Some(f)) + case InfoModeAnnotation(i) => c.copy(infoModeName = i) + case CompilerAnnotation(cx) => c.copy(compiler = cx) + case FirrtlCircuitAnnotation(cir) => c.copy(firrtlCircuit = Some(cir)) + } + } + } + + private [firrtl] implicit object FirrtlExecutionResultView extends OptionsView[FirrtlExecutionResult] { + + private lazy val dummyWriteEmitted = new WriteEmitted + + def view(options: AnnotationSeq): FirrtlExecutionResult = { + val fopts = Viewer[FirrtlOptions].view(options) + val emittedRes = options + .collect{ case DeletedAnnotation(dummyWriteEmitted.name, a: EmittedAnnotation[_]) => a.value.value } + .mkString("\n") + + options.collectFirst{ case a: FirrtlCircuitAnnotation => a.circuit } match { + case None => FirrtlExecutionFailure("No circuit found in AnnotationSeq!") + case Some(a) => FirrtlExecutionSuccess( + emitType = fopts.compiler.getClass.getSimpleName, + emitted = emittedRes, + circuitState = CircuitState( + circuit = a, + form = fopts.compiler.outputForm, + annotations = options, + renames = None + )) + } + } + } + +} diff --git a/src/main/scala/firrtl/stage/phases/AddCircuit.scala b/src/main/scala/firrtl/stage/phases/AddCircuit.scala new file mode 100644 index 00000000..f6ae6370 --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/AddCircuit.scala @@ -0,0 +1,59 @@ +// See LICENSE for license details. + +package firrtl.stage.phases + +import firrtl.stage._ + +import firrtl.{AnnotationSeq, Parser, proto} +import firrtl.options.{OptionsException, Phase, PhasePrerequisiteException} + +/** [[firrtl.options.Phase Phase]] that expands [[FirrtlFileAnnotation]]/[[FirrtlSourceAnnotation]] into + * [[FirrtlCircuitAnnotation]]s and deletes the originals. This is part of the preprocessing done on an input + * [[AnnotationSeq]] by [[FirrtlStage]]. + * + * The types of possible annotations are handled in the following ways: + * - [[FirrtlFileAnnotation]]s are read as Protocol Buffers if the file extension ends in `.pb`. Otherwise, these are + * assumed to be raw FIRRTL text and is sent to the [[Parser]]. The original [[FirrtlFileAnnotation]] is deleted. + * - [[FirrtlSourceAnnotation]]s are run through the [[Parser]]. The original [[FirrtlSourceAnnotation]] is deleted. + * - [[FirrtlCircuitAnnotation]]s are left untouched (along with all other annotations). + * + * If a [[Parser]] is used, its [[Parser.InfoMode InfoMode]] is read from a ''mandatory'' [[InfoModeAnnotation]]. If + * using an [[Parser.InfoMode InfoMode]] that expects a filename, the filename is used for [[FirrtlFileAnnotation]]s + * and `[anonymous source]` is used for [[FirrtlSourceAnnotation]]s. + * + * @note '''This must be run after [[AddDefaults]] as this [[firrtl.options.Phase Phase]] depends on the existence of + * an [[InfoModeAnnotation]].'''. + * @define infoModeException firrtl.options.PhasePrerequisiteException if no [[InfoModeAnnotation]] is present + */ +class AddCircuit extends Phase { + + /** Extract the info mode from an [[AnnotationSeq]] or use the default info mode if no annotation exists + * @param annotations some annotations + * @return the info mode + * @throws $infoModeException + */ + private def infoMode(annotations: AnnotationSeq): Parser.InfoMode = { + val infoModeAnnotation = annotations + .collectFirst{ case a: InfoModeAnnotation => a } + .getOrElse { throw new PhasePrerequisiteException( + "An InfoModeAnnotation must be present (did you forget to run AddDefaults?)") } + val infoSource = annotations.collectFirst{ + case FirrtlFileAnnotation(f) => f + case _: FirrtlSourceAnnotation => "anonymous source" + }.getOrElse("not defined") + + infoModeAnnotation.toInfoMode(Some(infoSource)) + } + + /** Convert [[FirrtlFileAnnotation]]/[[FirrtlSourceAnnotation]] into [[FirrtlCircuitAnnotation]] and delete originals + * @throws $infoModeException + */ + def transform(annotations: AnnotationSeq): AnnotationSeq = { + lazy val info = infoMode(annotations) + annotations.map { + case a: CircuitOption => a.toCircuit(info) + case a => a + } + } + +} diff --git a/src/main/scala/firrtl/stage/phases/AddDefaults.scala b/src/main/scala/firrtl/stage/phases/AddDefaults.scala new file mode 100644 index 00000000..dc8dc025 --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/AddDefaults.scala @@ -0,0 +1,36 @@ +// See LICENSE for license details. + +package firrtl.stage.phases + +import firrtl.AnnotationSeq +import firrtl.options.{Phase, TargetDirAnnotation} +import firrtl.transforms.BlackBoxTargetDirAnno +import firrtl.stage.{CompilerAnnotation, InfoModeAnnotation, FirrtlOptions} + +/** [[firrtl.options.Phase Phase]] that adds default [[FirrtlOption]] [[firrtl.annotations.Annotation Annotation]]s. + * This is a part of the preprocessing done by [[FirrtlStage]]. + */ +class AddDefaults extends Phase { + + /** Append any missing default annotations to an annotation sequence */ + def transform(annotations: AnnotationSeq): AnnotationSeq = { + var bb, c, im = true + annotations.foreach { + case _: BlackBoxTargetDirAnno => bb = false + case _: CompilerAnnotation => c = false + case _: InfoModeAnnotation => im = false + case a => + } + + val default = new FirrtlOptions() + val targetDir = annotations + .collectFirst { case d: TargetDirAnnotation => d } + .getOrElse(TargetDirAnnotation()).directory + + (if (bb) Seq(BlackBoxTargetDirAnno(targetDir)) else Seq() ) ++ + (if (c) Seq(CompilerAnnotation(default.compiler)) else Seq() ) ++ + (if (im) Seq(InfoModeAnnotation()) else Seq() ) ++ + annotations + } + +} diff --git a/src/main/scala/firrtl/stage/phases/AddImplicitEmitter.scala b/src/main/scala/firrtl/stage/phases/AddImplicitEmitter.scala new file mode 100644 index 00000000..84f98cdb --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/AddImplicitEmitter.scala @@ -0,0 +1,30 @@ +// See LICENSE for license details. + +package firrtl.stage.phases + +import firrtl.{AnnotationSeq, EmitAnnotation, EmitCircuitAnnotation} +import firrtl.stage.{CompilerAnnotation, RunFirrtlTransformAnnotation} +import firrtl.options.Phase + +/** [[firrtl.options.Phase Phase]] that adds a [[firrtl.EmitCircuitAnnotation EmitCircuitAnnotation]] derived from a + * [[firrtl.stage.CompilerAnnotation CompilerAnnotation]] if one does not already exist. + */ +class AddImplicitEmitter extends Phase { + + def transform(annos: AnnotationSeq): AnnotationSeq = { + val emitter = annos.collectFirst{ case a: EmitAnnotation => a } + val compiler = annos.collectFirst{ case CompilerAnnotation(a) => a } + + if (emitter.isEmpty && compiler.nonEmpty) { + annos.flatMap{ + case a: CompilerAnnotation => Seq(a, + RunFirrtlTransformAnnotation(compiler.get.emitter), + EmitCircuitAnnotation(compiler.get.emitter.getClass)) + case a => Some(a) + } + } else { + annos + } + } + +} diff --git a/src/main/scala/firrtl/stage/phases/AddImplicitOutputFile.scala b/src/main/scala/firrtl/stage/phases/AddImplicitOutputFile.scala new file mode 100644 index 00000000..4b4308b6 --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/AddImplicitOutputFile.scala @@ -0,0 +1,34 @@ +// See LICENSE for license details. + +package firrtl.stage.phases + +import firrtl.{AnnotationSeq, EmitAllModulesAnnotation} +import firrtl.options.{Phase, Viewer} +import firrtl.stage.{FirrtlCircuitAnnotation, FirrtlOptions, OutputFileAnnotation} + +/** [[firrtl.options.Phase Phase]] that adds an [[OutputFileAnnotation]] if one does not already exist. + * + * To determine the [[OutputFileAnnotation]], the following precedence is used. Whichever happens first succeeds: + * - Do nothing if an [[OutputFileAnnotation]] or [[EmitAllModulesAnnotation]] exist + * - Use the main in the first discovered [[FirrtlCircuitAnnotation]] (see note below) + * - Use "a" + * + * The file suffix may or may not be specified, but this may be arbitrarily changed by the [[Emitter]]. + * + * @note This [[firrtl.options.Phase Phase]] has a dependency on [[AddCircuit]]. Only a [[FirrtlCircuitAnnotation]] + * will be used to implicitly set the [[OutputFileAnnotation]] (not other [[CircuitOption]] subclasses). + */ +class AddImplicitOutputFile extends Phase { + + /** Add an [[OutputFileAnnotation]] to an [[AnnotationSeq]] */ + def transform(annotations: AnnotationSeq): AnnotationSeq = + annotations + .collectFirst { case _: OutputFileAnnotation | _: EmitAllModulesAnnotation => annotations } + .getOrElse { + val topName = Viewer[FirrtlOptions].view(annotations) + .firrtlCircuit + .map(_.main) + .getOrElse("a") + OutputFileAnnotation(topName) +: annotations + } +} diff --git a/src/main/scala/firrtl/stage/phases/Checks.scala b/src/main/scala/firrtl/stage/phases/Checks.scala new file mode 100644 index 00000000..19109dab --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/Checks.scala @@ -0,0 +1,93 @@ +// See LICENSE for license details. + +package firrtl.stage.phases + +import firrtl.stage._ + +import firrtl.{AnnotationSeq, EmitAllModulesAnnotation, EmitCircuitAnnotation} +import firrtl.annotations.Annotation +import firrtl.options.{OptionsException, Phase, StageUtils} + +/** [[firrtl.options.Phase Phase]] that strictly validates an [[AnnotationSeq]]. The checks applied are intended to be + * extremeley strict. Nothing is inferred or assumed to take a default value (for default value resolution see + * [[AddDefaults]]). + * + * The intent of this approach is that after running this [[firrtl.options.Phase Phase]], a user can be absolutely + * certain that other [[firrtl.options.Phase Phase]]s or views will succeed. See [[FirrtlStage]] for a list of + * [[firrtl.options.Phase Phase]] that commonly run before this. + */ +class Checks extends Phase { + + /** Determine if annotations are sane + * + * @param annos a sequence of [[annotation.Annotation]] + * @return true if all checks pass + * @throws firrtl.options.OptionsException if any checks fail + */ + def transform(annos: AnnotationSeq): AnnotationSeq = { + val inF, inS, eam, ec, outF, td, i, foaf, comp, im, inC = collection.mutable.ListBuffer[Annotation]() + annos.foreach( + _ match { + case a: FirrtlFileAnnotation => a +=: inF + case a: FirrtlSourceAnnotation => a +=: inS + case a: EmitAllModulesAnnotation => a +=: eam + case a: EmitCircuitAnnotation => a +=: ec + case a: OutputFileAnnotation => a +=: outF + case a: CompilerAnnotation => a +=: comp + case a: InfoModeAnnotation => a +=: im + case a: FirrtlCircuitAnnotation => a +=: inC + case _ => }) + + /* At this point, only a FIRRTL Circuit should exist */ + if (inF.isEmpty && inS.isEmpty && inC.isEmpty) { + throw new OptionsException( + s"""|Unable to determine FIRRTL source to read. None of the following were found: + | - an input file: -i, --input-file, FirrtlFileAnnotation + | - FIRRTL source: --firrtl-source, FirrtlSourceAnnotation + | - FIRRTL circuit: FirrtlCircuitAnnotation""".stripMargin )} + + /* Only one FIRRTL input can exist */ + if (inF.size + inS.size + inC.size > 1) { + throw new OptionsException( + s"""|Multiply defined input FIRRTL sources. More than one of the following was found: + | - an input file (${inF.size} times): -i, --input-file, FirrtlFileAnnotation + | - FIRRTL source (${inS.size} times): --firrtl-source, FirrtlSourceAnnotation + | - FIRRTL circuit (${inC.size} times): FirrtlCircuitAnnotation""".stripMargin )} + + /* Specifying an output file and one-file-per module conflict */ + if (eam.nonEmpty && outF.nonEmpty) { + throw new OptionsException( + s"""|Output file is incompatible with emit all modules annotation, but multiples were found: + | - explicit output file (${outF.size} times): -o, --output-file, OutputFileAnnotation + | - one file per module (${eam.size} times): -e, --emit-modules, EmitAllModulesAnnotation""" + .stripMargin )} + + /* Only one output file can be specified */ + if (outF.size > 1) { + val x = outF.map{ case OutputFileAnnotation(x) => x } + throw new OptionsException( + s"""|No more than one output file can be specified, but found '${x.mkString(", ")}' specified via: + | - option or annotation: -o, --output-file, OutputFileAnnotation""".stripMargin) } + + /* One mandatory compiler must be specified */ + if (comp.size != 1) { + val x = comp.map{ case CompilerAnnotation(x) => x } + val (msg, suggest) = if (comp.size == 0) { ("none found", "forget one of") } + else { (s"""found '${x.mkString(", ")}'""", "use multiple of") } + throw new OptionsException( + s"""|Exactly one compiler must be specified, but $msg. Did you $suggest the following? + | - an option or annotation: -X, --compiler, CompilerAnnotation""".stripMargin )} + + /* One mandatory info mode must be specified */ + if (im.size != 1) { + val x = im.map{ case InfoModeAnnotation(x) => x } + val (msg, suggest) = if (im.size == 0) { ("none found", "forget one of") } + else { (s"""found '${x.mkString(", ")}'""", "use multiple of") } + throw new OptionsException( + s"""|Exactly one info mode must be specified, but $msg. Did you $suggest the following? + | - an option or annotation: --info-mode, InfoModeAnnotation""".stripMargin )} + + annos + } + +} diff --git a/src/main/scala/firrtl/stage/phases/Compiler.scala b/src/main/scala/firrtl/stage/phases/Compiler.scala new file mode 100644 index 00000000..4d46f8a0 --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/Compiler.scala @@ -0,0 +1,99 @@ +// See LICENSE for license details. + +package firrtl.stage.phases + +import firrtl.{AnnotationSeq, ChirrtlForm, CircuitState, Compiler => FirrtlCompiler, Transform, seqToAnnoSeq} +import firrtl.options.{Phase, PhasePrerequisiteException, Translator} +import firrtl.stage.{CircuitOption, CompilerAnnotation, FirrtlOptions, FirrtlCircuitAnnotation, + RunFirrtlTransformAnnotation} + +import scala.collection.mutable + +/** An encoding of the information necessary to run the FIRRTL compiler once */ +private [stage] case class CompilerRun( + stateIn: CircuitState, + stateOut: Option[CircuitState], + transforms: Seq[Transform], + compiler: Option[FirrtlCompiler] ) + +/** An encoding of possible defaults for a [[CompilerRun]] */ +private [stage] case class Defaults( + annotations: AnnotationSeq = Seq.empty, + transforms: Seq[Transform] = Seq.empty, + compiler: Option[FirrtlCompiler] = None) + +/** Runs the FIRRTL compilers on an [[AnnotationSeq]]. If the input [[AnnotationSeq]] contains more than one circuit + * (i.e., more than one [[FirrtlCircuitAnnotation]]), then annotations will be broken up and each run will be executed + * in parallel. + * + * The [[AnnotationSeq]] will be chunked up into compiler runs using the following algorithm. All annotations that + * occur before the first [[FirrtlCircuitAnnotation]] are treated as global annotations that apply to all circuits. + * Annotations after a circuit are only associated with their closest preceeding circuit. E.g., for the following + * annotations (where A, B, and C are some annotations): + * + * A(a), FirrtlCircuitAnnotation(x), B, FirrtlCircuitAnnotation(y), A(b), C, FirrtlCircuitAnnotation(z) + * + * Then this will result in three compiler runs: + * - FirrtlCircuitAnnotation(x): A(a), B + * - FirrtlCircuitAnnotation(y): A(a), A(b), C + * - FirrtlCircuitAnnotation(z): A(a) + * + * A(a) is a default, global annotation. B binds to FirrtlCircuitAnnotation(x). A(a), A(b), and C bind to + * FirrtlCircuitAnnotation(y). Note: A(b) ''may'' overwrite A(a) if this is a CompilerAnnotation. + * FirrtlCircuitAnnotation(z) has no annotations, so it only gets the default A(a). + */ +class Compiler extends Phase with Translator[AnnotationSeq, Seq[CompilerRun]] { + + /** Convert an [[AnnotationSeq]] into a sequence of compiler runs. */ + protected def aToB(a: AnnotationSeq): Seq[CompilerRun] = { + var foundFirstCircuit = false + val c = mutable.ArrayBuffer.empty[CompilerRun] + a.foldLeft(Defaults()){ + case (d, FirrtlCircuitAnnotation(circuit)) => + foundFirstCircuit = true + CompilerRun(CircuitState(circuit, ChirrtlForm, d.annotations, None), None, d.transforms, d.compiler) +=: c + d + case (d, a) if foundFirstCircuit => a match { + case RunFirrtlTransformAnnotation(transform) => + c(0) = c(0).copy(transforms = transform +: c(0).transforms) + d + case CompilerAnnotation(compiler) => + c(0) = c(0).copy(compiler = Some(compiler)) + d + case annotation => + val state = c(0).stateIn + c(0) = c(0).copy(stateIn = state.copy(annotations = annotation +: state.annotations)) + d + } + case (d, a) if !foundFirstCircuit => a match { + case RunFirrtlTransformAnnotation(transform) => d.copy(transforms = transform +: d.transforms) + case CompilerAnnotation(compiler) => d.copy(compiler = Some(compiler)) + case annotation => d.copy(annotations = annotation +: d.annotations) + } + } + c + } + + /** Expand compiler output back into an [[AnnotationSeq]]. Annotations used in the construction of the compiler run are + * removed ([[CompilerAnnotation]]s and [[RunFirrtlTransformAnnotation]]s). + */ + protected def bToA(b: Seq[CompilerRun]): AnnotationSeq = + b.flatMap( bb => FirrtlCircuitAnnotation(bb.stateOut.get.circuit) +: bb.stateOut.get.annotations ) + + /** Run the FIRRTL compiler some number of times. If more than one run is specified, a parallel collection will be + * used. + */ + protected def internalTransform(b: Seq[CompilerRun]): Seq[CompilerRun] = { + def f(c: CompilerRun): CompilerRun = { + val statex = c + .compiler + .getOrElse { throw new PhasePrerequisiteException("No compiler specified!") } + .compile(c.stateIn, c.transforms.reverse) + c.copy(stateOut = Some(statex)) + } + + if (b.size <= 1) { b.map(f) } + else { b.par.map(f).seq } + } + +} diff --git a/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala b/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala new file mode 100644 index 00000000..cb6a135d --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala @@ -0,0 +1,224 @@ +// See LICENSE for license details. + +package firrtl.stage.phases + +import firrtl.stage._ + +import firrtl.{AnnotationSeq, EmitAllModulesAnnotation, EmitCircuitAnnotation, FirrtlExecutionResult, Parser} +import firrtl.annotations.NoTargetAnnotation +import firrtl.proto.FromProto +import firrtl.options.{HasShellOptions, InputAnnotationFileAnnotation, OptionsException, Phase, ShellOption, + StageOptions, StageUtils} +import firrtl.options.Viewer + +import scopt.OptionParser + +import java.io.File + +/** Provides compatibility methods to replicate deprecated [[Driver]] semantics. + * + * At a high level, the [[Driver]] tries extremely hard to figure out what the user meant and to enable them to not be + * explicit with command line options. As an example, the `--top-name` option is not used for any FIRRTL top module + * determination, but to find a FIRRTL file by that name and/or an annotation file by that name. This mode of file + * discovery is only used if no explicit FIRRTL file/source/circuit and/or annotation file is given. Going further, the + * `--top-name` argument is implicitly specified by the `main` of an input circuit if not explicit and can be used to + * derive an annotation file. Summarily, the [[firrtl.options.Phase Phase]]s provided by this enable this type of + * resolution. + * + * '''Only use these methods if you are intending to replicate old [[Driver]] semantics for a good reason.''' + * Otherwise, opt for more explicit specification by the user. + */ +object DriverCompatibility { + + /** Shorthand object for throwing an exception due to an option that was removed */ + private def optionRemoved(a: String): String = + s"""|Option '$a' was removed as part of the FIRRTL Stage refactor. Use an explicit input/output options instead. + |This error will be removed in 1.3.""".stripMargin + + /** Convert an [[firrtl.AnnotationSeq AnnotationSeq]] to a ''deprecated'' [[firrtl.FirrtlExecutionResult + * FirrtlExecutionResult]]. + * @param annotations a sequence of [[firrtl.annotations.Annotation Annotation]] + */ + @deprecated("FirrtlExecutionResult is deprecated as part of the Stage/Phase refactor. Migrate to FirrtlStage.", "1.2") + def firrtlResultView(annotations: AnnotationSeq): FirrtlExecutionResult = + Viewer[FirrtlExecutionResult].view(annotations) + + /** Holds the name of the top (main) module in an input circuit + * @param value top module name + */ + @deprecated(""""top-name" is deprecated as part of the Stage/Phase refactor. Use explicit input/output files.""", "1.2") + case class TopNameAnnotation(topName: String) extends NoTargetAnnotation + + object TopNameAnnotation { + + def addOptions(p: OptionParser[AnnotationSeq]): Unit = p + .opt[Unit]("top-name") + .abbr("tn") + .hidden + .unbounded + .action( (_, _) => throw new OptionsException(optionRemoved("--top-name/-tn")) ) + } + + /** Indicates that the implicit emitter, derived from a [[CompilerAnnotation]] should be an [[EmitAllModulesAnnotation]] + * as opposed to an [[EmitCircuitAnnotation]]. + */ + private [firrtl] case object EmitOneFilePerModuleAnnotation extends NoTargetAnnotation { + + def addOptions(p: OptionParser[AnnotationSeq]): Unit = p + .opt[Unit]("split-modules") + .abbr("fsm") + .hidden + .unbounded + .action( (_, _) => throw new OptionsException(optionRemoved("--split-modules/-fsm")) ) + + } + + /** Determine the top name using the following precedence (highest to lowest): + * - Explicitly from a [[TopNameAnnotation]] + * - Implicitly from the top module ([[firrtl.ir.Circuit.main main]]) of a [[FirrtlCircuitAnnotation]] + * - Implicitly from the top module ([[firrtl.ir.Circuit.main main]]) of a [[FirrtlSourceAnnotation]] + * - Implicitly from the top module ([[firrtl.ir.Circuit.main main]]) of a [[FirrtlFileAnnotation]] + * + * @param annotations annotations to extract topName from + * @return the top module ''if it can be determined'' + */ + private def topName(annotations: AnnotationSeq): Option[String] = + annotations.collectFirst{ case TopNameAnnotation(n) => n }.orElse( + annotations.collectFirst{ case FirrtlCircuitAnnotation(c) => c.main }.orElse( + annotations.collectFirst{ case FirrtlSourceAnnotation(s) => Parser.parse(s).main }.orElse( + annotations.collectFirst{ case FirrtlFileAnnotation(f) => + FirrtlStageUtils.getFileExtension(f) match { + case ProtoBufFile => FromProto.fromFile(f).main + case FirrtlFile => Parser.parse(io.Source.fromFile(f).getLines().mkString("\n")).main } } ))) + + /** Determine the target directory with the following precedence (highest to lowest): + * - Explicitly from the user-specified [[firrtl.options.TargetDirAnnotation TargetDirAnnotation]] + * - Implicitly from the default of [[firrtl.options.StageOptions.targetDir StageOptions.targetDir]] + * + * @param annotations input annotations to extract targetDir from + * @return the target directory + */ + private def targetDir(annotations: AnnotationSeq): String = Viewer[StageOptions].view(annotations).targetDir + + /** Add an implicit annotation file derived from the determined top name of the circuit if no + * [[firrtl.options.InputAnnotationFileAnnotation InputAnnotationFileAnnotation]] is present. + * + * The implicit annotation file is determined through the following complicated semantics: + * - If an [[InputAnnotationFileAnnotation]] already exists, then nothing is modified + * - If the derived topName (the `main` in a [[firrtl.ir.Circuit Circuit]]) is ''discernable'' (see below) and a + * file called `topName.anno` (exactly, not `topName.anno.json`) exists, then this will add an + * [[InputAnnotationFileAnnotation]] using that `topName.anno` + * - If any of this doesn't work, then the the [[AnnotationSeq]] is unmodified + * + * The precedence for determining the `topName` is the following (first one wins): + * - The `topName` in a [[TopNameAnnotation]] + * - The `main` [[FirrtlCircuitAnnotation]] + * - The `main` in a parsed [[FirrtlSourceAnnotation]] + * - The `main` in the first [[FirrtlFileAnnotation]] using either ProtoBuf or parsing as determined by file + * extension + * + * @param annos input annotations + * @return output annotations + */ + class AddImplicitAnnotationFile extends Phase { + + /** Try to add an [[InputAnnotationFileAnnotation]] implicitly specified by an [[AnnotationSeq]]. */ + def transform(annotations: AnnotationSeq): AnnotationSeq = annotations + .collectFirst{ case a: InputAnnotationFileAnnotation => a } match { + case Some(_) => annotations + case None => topName(annotations) match { + case Some(n) => + val filename = targetDir(annotations) + "/" + n + ".anno" + if (new File(filename).exists) { + StageUtils.dramaticWarning( + s"Implicit reading of the annotation file is deprecated! Use an explict --annotation-file argument.") + annotations :+ InputAnnotationFileAnnotation(filename) + } else { + annotations + } + case None => annotations + } } + + } + + /** Add a [[FirrtlFileAnnotation]] if no annotation that explictly defines a circuit exists. + * + * This takes the option with the following precedence: + * - If an annotation subclassing [[CircuitOption]] exists, do nothing + * - If a [[TopNameAnnotation]] exists, use that to derive a [[FirrtlFileAnnotation]] and append it + * - Do nothing + * + * In the case of (3) above, this [[AnnotationSeq]] is likely insufficient for FIRRTL to work with (no circuit was + * passed). However, instead of catching this here, we rely on [[Checks]] to validate the annotations. + * + * @param annotations input annotations + * @return + */ + class AddImplicitFirrtlFile extends Phase { + + /** Try to add a [[FirrtlFileAnnotation]] implicitly specified by an [[AnnotationSeq]]. */ + def transform(annotations: AnnotationSeq): AnnotationSeq = { + val circuit = annotations.collectFirst { case a @ (_: CircuitOption | _: FirrtlCircuitAnnotation) => a } + val main = annotations.collectFirst { case a: TopNameAnnotation => a.topName } + + if (circuit.nonEmpty) { + annotations + } else if (main.nonEmpty) { + StageUtils.dramaticWarning( + s"Implicit reading of the input file is deprecated! Use an explict --input-file argument.") + FirrtlFileAnnotation(Viewer[StageOptions].view(annotations).getBuildFileName(s"${main.get}.fir")) +: annotations + } else { + annotations + } + } + } + + /** Adds an [[EmitAnnotation]] for each [[CompilerAnnotation]]. + * + * If an [[EmitOneFilePerModuleAnnotation]] exists, then this will add an [[EmitAllModulesAnnotation]]. Otherwise, + * this adds an [[EmitCircuitAnnotation]]. This replicates old behavior where specifying a compiler automatically + * meant that an emitter would also run. + */ + @deprecated("""AddImplicitEmitter should only be used to build Driver compatibility wrappers. Switch to Stage.""", + "1.2") + class AddImplicitEmitter extends Phase { + + /** Add one [[EmitAnnotation]] foreach [[CompilerAnnotation]]. */ + def transform(annotations: AnnotationSeq): AnnotationSeq = { + val splitModules = annotations.collectFirst{ case a: EmitOneFilePerModuleAnnotation.type => a }.isDefined + + annotations.flatMap { + case a @ CompilerAnnotation(c) => + val b = RunFirrtlTransformAnnotation(a.compiler.emitter) + if (splitModules) { Seq(a, b, EmitAllModulesAnnotation(c.emitter.getClass)) } + else { Seq(a, b, EmitCircuitAnnotation (c.emitter.getClass)) } + case a => Seq(a) + } + } + + } + + /** Adds an [[OutputFileAnnotation]] derived from a [[TopNameAnnotation]] if no [[OutputFileAnnotation]] already + * exists. If no [[TopNameAnnotation]] exists, then no [[OutputFileAnnotation]] is added. + */ + @deprecated("""AddImplicitOutputFile should only be used to build Driver compatibility wrappers. Switch to Stage.""", + "1.2") + class AddImplicitOutputFile extends Phase { + + /** Add an [[OutputFileAnnotation]] derived from a [[TopNameAnnotation]] if needed. */ + def transform(annotations: AnnotationSeq): AnnotationSeq = { + val hasOutputFile = annotations + .collectFirst{ case a @(_: EmitOneFilePerModuleAnnotation.type | _: OutputFileAnnotation) => a } + .isDefined + val top = topName(annotations) + + if (!hasOutputFile && top.isDefined) { + OutputFileAnnotation(top.get) +: annotations + } else { + annotations + } + } + + } + +} diff --git a/src/main/scala/firrtl/stage/phases/WriteEmitted.scala b/src/main/scala/firrtl/stage/phases/WriteEmitted.scala new file mode 100644 index 00000000..7c38ebbf --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/WriteEmitted.scala @@ -0,0 +1,50 @@ +// See LICENSE for license details. + +package firrtl.stage.phases + +import firrtl.{AnnotationSeq, EmittedModuleAnnotation, EmittedCircuitAnnotation} +import firrtl.options.{Phase, StageOptions, Viewer} +import firrtl.stage.FirrtlOptions + +import java.io.PrintWriter + +/** [[firrtl.options.Phase Phase]] that writes any [[EmittedAnnotation]]s in an input [[AnnotationSeq]] to one or more + * files. The input [[AnnotationSeq]] is viewed as both [[FirrtlOptions]] and [[firrtl.options.StageOptions + * StageOptions]] to determine the output filenames in the following way: + * - [[EmittedModuleAnnotation]]s are written to a file in [[firrtl.options.StageOptions.targetDir + * StageOptions.targetDir]] with the same name as the module and the [[EmittedComponent.outputSuffix outputSuffix]] + * that the [[EmittedComponent]] specified + * - [[EmittedCircuitAnnotation]]s are written to a file in [[firrtl.options.StageOptions.targetDir + * StageOptions.targetDir]] using the [[FirrtlOptions.outputFileName]] viewed from the [[AnnotationSeq]]. If no + * [[FirrtlOptions.outputFileName]] exists, then the top module/main name will be used. The + * [[EmittedComponent.outputSuffix outputSuffix]] will be appended as needed. + * + * This does no sanity checking of the input [[AnnotationSeq]]. This simply writes any modules or circuits it sees to + * files. If you need additional checking, then you should stack an appropriate checking phase before this. + * + * Any annotations written to files will be deleted. + */ +class WriteEmitted extends Phase { + + /** Write any [[EmittedAnnotation]]s in an [[AnnotationSeq]] to files. Written [[EmittedAnnotation]]s are deleted. */ + def transform(annotations: AnnotationSeq): AnnotationSeq = { + val fopts = Viewer[FirrtlOptions].view(annotations) + val sopts = Viewer[StageOptions].view(annotations) + + annotations.flatMap { + case a: EmittedModuleAnnotation[_] => + val pw = new PrintWriter(sopts.getBuildFileName(a.value.name, Some(a.value.outputSuffix))) + pw.write(a.value.value) + pw.close() + None + case a: EmittedCircuitAnnotation[_] => + val pw = new PrintWriter( + sopts.getBuildFileName(fopts.outputFileName.getOrElse(a.value.name), Some(a.value.outputSuffix))) + pw.write(a.value.value) + pw.close() + None + case a => Some(a) + } + + } +} diff --git a/src/main/scala/firrtl/transforms/CheckCombLoops.scala b/src/main/scala/firrtl/transforms/CheckCombLoops.scala index 0a9ec0e3..17e1569b 100644 --- a/src/main/scala/firrtl/transforms/CheckCombLoops.scala +++ b/src/main/scala/firrtl/transforms/CheckCombLoops.scala @@ -17,7 +17,7 @@ import firrtl.annotations._ import firrtl.Utils.throwInternalError import firrtl.graph.{MutableDiGraph,DiGraph} import firrtl.analyses.InstanceGraph -import firrtl.options.RegisteredTransform +import firrtl.options.{RegisteredTransform, ShellOption} import scopt.OptionParser object CheckCombLoops { @@ -69,11 +69,11 @@ class CheckCombLoops extends Transform with RegisteredTransform { import CheckCombLoops._ - def addOptions(parser: OptionParser[AnnotationSeq]): Unit = parser - .opt[Unit]("no-check-comb-loops") - .action( (x, c) => c :+ DontCheckCombLoopsAnnotation ) - .maxOccurs(1) - .text("Do NOT check for combinational loops (not recommended)") + val options = Seq( + new ShellOption[Unit]( + longOption = "no-check-comb-loops", + toAnnotationSeq = (_: Unit) => Seq(DontCheckCombLoopsAnnotation), + helpText = "Disable combinational loop checking" ) ) /* * A case class that represents a net in the circuit. This is diff --git a/src/main/scala/firrtl/transforms/DeadCodeElimination.scala b/src/main/scala/firrtl/transforms/DeadCodeElimination.scala index deb7299d..0c357267 100644 --- a/src/main/scala/firrtl/transforms/DeadCodeElimination.scala +++ b/src/main/scala/firrtl/transforms/DeadCodeElimination.scala @@ -11,7 +11,7 @@ import firrtl.Mappers._ import firrtl.WrappedExpression._ import firrtl.Utils.{throwInternalError, toWrappedExpression, kind} import firrtl.MemoizedHash._ -import firrtl.options.RegisteredTransform +import firrtl.options.{RegisteredTransform, ShellOption} import scopt.OptionParser import collection.mutable @@ -36,11 +36,11 @@ class DeadCodeElimination extends Transform with ResolvedAnnotationPaths with Re def inputForm = LowForm def outputForm = LowForm - def addOptions(parser: OptionParser[AnnotationSeq]): Unit = parser - .opt[Unit]("no-dce") - .action( (x, c) => c :+ NoDCEAnnotation ) - .maxOccurs(1) - .text("Do NOT run dead code elimination") + val options = Seq( + new ShellOption[Unit]( + longOption = "no-dce", + toAnnotationSeq = (_: Unit) => Seq(NoDCEAnnotation), + helpText = "Disable dead code elimination" ) ) /** Based on LogicNode ins CheckCombLoops, currently kind of faking it */ private type LogicNode = MemoizedHash[WrappedExpression] diff --git a/src/main/scala/firrtl/util/BackendCompilationUtilities.scala b/src/main/scala/firrtl/util/BackendCompilationUtilities.scala index bd5d2a9a..34be1d1e 100644 --- a/src/main/scala/firrtl/util/BackendCompilationUtilities.scala +++ b/src/main/scala/firrtl/util/BackendCompilationUtilities.scala @@ -10,7 +10,7 @@ import java.util.Calendar import firrtl.FirrtlExecutionOptions import scala.sys.process.{ProcessBuilder, ProcessLogger, _} - + trait BackendCompilationUtilities { /** Parent directory for tests */ lazy val TestDirectory = new File("test_run_dir") diff --git a/src/main/scala/logger/Logger.scala b/src/main/scala/logger/Logger.scala index 6e8dbeb1..00c29b1a 100644 --- a/src/main/scala/logger/Logger.scala +++ b/src/main/scala/logger/Logger.scala @@ -4,7 +4,12 @@ package logger import java.io.{ByteArrayOutputStream, File, FileOutputStream, PrintStream} -import firrtl.ExecutionOptionsManager +import firrtl.{ExecutionOptionsManager, HasFirrtlOptions, AnnotationSeq} +import firrtl.stage.FirrtlOptions +import firrtl.options.StageOptions +import firrtl.options.Viewer.view +import firrtl.stage.FirrtlOptionsView +import logger.phases.{AddDefaults, Checks} import scala.util.DynamicVariable @@ -120,24 +125,9 @@ object Logger { * @tparam A The return type of codeBlock * @return Whatever block returns */ - def makeScope[A](manager: ExecutionOptionsManager)(codeBlock: => A): A = { - val runState: LoggerState = { - val newRunState = updatableLoggerState.value.getOrElse(new LoggerState) - if(newRunState.fromInvoke) { - newRunState - } - else { - val forcedNewRunState = new LoggerState - forcedNewRunState.fromInvoke = true - forcedNewRunState - } - } - - updatableLoggerState.withValue(Some(runState)) { - setOptions(manager) - codeBlock - } - } + @deprecated("Use makeScope(opts: FirrtlOptions)", "1.2") + def makeScope[A](manager: ExecutionOptionsManager)(codeBlock: => A): A = + makeScope(manager.commonOptions.toAnnotations)(codeBlock) /** * See makeScope using manager. This creates a manager from a command line arguments style @@ -147,6 +137,7 @@ object Logger { * @tparam A return type of codeBlock * @return */ + @deprecated("Use makescope(opts: FirrtlOptions)", "1.2") def makeScope[A](args: Array[String] = Array.empty)(codeBlock: => A): A = { val executionOptionsManager = new ExecutionOptionsManager("logger") if(executionOptionsManager.parse(args)) { @@ -157,6 +148,29 @@ object Logger { } } + /** Set a scope for this logger based on available annotations + * @param options a sequence annotations + * @param codeBlock some Scala code over which to define this scope + * @tparam A return type of the code block + * @return the original return of the code block + */ + def makeScope[A](options: AnnotationSeq)(codeBlock: => A): A = { + val runState: LoggerState = { + val newRunState = updatableLoggerState.value.getOrElse(new LoggerState) + if(newRunState.fromInvoke) { + newRunState + } + else { + val forcedNewRunState = new LoggerState + forcedNewRunState.fromInvoke = true + forcedNewRunState + } + } + updatableLoggerState.withValue(Some(runState)) { + setOptions(options) + codeBlock + } + } /** * Used to test whether a given log statement should generate some logging output. @@ -341,20 +355,33 @@ object Logger { * from the command line via an options manager * @param optionsManager manager */ - def setOptions(optionsManager: ExecutionOptionsManager): Unit = { - val commonOptions = optionsManager.commonOptions - state.globalLevel = (state.globalLevel, commonOptions.globalLogLevel) match { + @deprecated("Use setOptions(annotations: AnnotationSeq)", "1.2") + def setOptions(optionsManager: ExecutionOptionsManager): Unit = + setOptions(optionsManager.commonOptions.toAnnotations) + + /** Set logger options based on the content of an [[firrtl.AnnotationSeq AnnotationSeq]] + * @param inputAnnotations annotation sequence containing logger options + */ + def setOptions(inputAnnotations: AnnotationSeq): Unit = { + val annotations = + Seq( AddDefaults, Checks ) + .foldLeft(inputAnnotations)((a, p) => p.transform(a)) + + val lopts = view[LoggerOptions](annotations) + state.globalLevel = (state.globalLevel, lopts.globalLogLevel) match { case (LogLevel.None, LogLevel.None) => LogLevel.None case (x, LogLevel.None) => x case (LogLevel.None, x) => x case (_, x) => x case _ => LogLevel.Error } - setClassLogLevels(commonOptions.classLogLevels) - if(commonOptions.logToFile) { - setOutput(commonOptions.getLogFileName(optionsManager)) + setClassLogLevels(lopts.classLogLevels) + + if (lopts.logFileName.nonEmpty) { + setOutput(lopts.logFileName.get) } - state.logClassNames = commonOptions.logClassNames + + state.logClassNames = lopts.logClassNames } } @@ -399,3 +426,9 @@ class Logger(containerClass: String) { Logger.showMessage(LogLevel.Trace, containerClass, message) } } + +/** An exception originating from the Logger + * @param str an exception message + * @param cause a reason for the exception + */ +class LoggerException(val str: String, cause: Throwable = null) extends RuntimeException(str, cause) diff --git a/src/main/scala/logger/LoggerAnnotations.scala b/src/main/scala/logger/LoggerAnnotations.scala new file mode 100644 index 00000000..204fc4ab --- /dev/null +++ b/src/main/scala/logger/LoggerAnnotations.scala @@ -0,0 +1,84 @@ +// See LICENSE for license details. + +package logger + +import firrtl.AnnotationSeq +import firrtl.annotations.{Annotation, NoTargetAnnotation} +import firrtl.options.{HasShellOptions, ShellOption, StageUtils} + +import scopt.OptionParser + +/** An annotation associated with a Logger command line option */ +sealed trait LoggerOption { this: Annotation => } + +/** Describes the verbosity of information to log + * - set with `-ll/--log-level` + * - if unset, a [[LogLevelAnnotation]] with the default log level will be emitted + * @param level the level of logging + */ +case class LogLevelAnnotation(globalLogLevel: LogLevel.Value = LogLevel.None) extends NoTargetAnnotation with LoggerOption + +object LogLevelAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "log-level", + toAnnotationSeq = (a: String) => Seq(LogLevelAnnotation(LogLevel(a))), + helpText = s"Set global logging verbosity (default: ${new LoggerOptions().globalLogLevel}", + shortOption = Some("ll"), + helpValueName = Some("{error|warn|info|debug|trace}") ) ) + +} + +/** Describes a mapping of a class to a specific log level + * - set with `-cll/--class-log-level` + * @param name the class name to log + * @param level the verbosity level + */ +case class ClassLogLevelAnnotation(className: String, level: LogLevel.Value) extends NoTargetAnnotation with LoggerOption + +object ClassLogLevelAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[Seq[String]]( + longOption = "class-log-level", + toAnnotationSeq = (a: Seq[String]) => a.map { aa => + val className :: levelName :: _ = aa.split(":").toList + val level = LogLevel(levelName) + ClassLogLevelAnnotation(className, level) }, + helpText = "Set per-class logging verbosity", + shortOption = Some("cll"), + helpValueName = Some("<FullClassName:{error|warn|info|debug|trace}>...") ) ) + +} + +/** Enables logging to a file (as opposed to STDOUT) + * - maps to [[LoggerOptions.logFileName]] + * - enabled with `--log-file` + */ +case class LogFileAnnotation(file: Option[String]) extends NoTargetAnnotation with LoggerOption + +object LogFileAnnotation extends HasShellOptions { + + val options = Seq( + new ShellOption[String]( + longOption = "log-file", + toAnnotationSeq = (a: String) => Seq(LogFileAnnotation(Some(a))), + helpText = "Log to a file instead of STDOUT", + helpValueName = Some("<file>") ) ) + +} + +/** Enables class names in log output + * - enabled with `-lcn/--log-class-names` + */ +case object LogClassNamesAnnotation extends NoTargetAnnotation with LoggerOption with HasShellOptions { + + val options = Seq( + new ShellOption[Unit]( + longOption = "log-class-names", + toAnnotationSeq = (a: Unit) => Seq(LogClassNamesAnnotation), + helpText = "Show class names and log level in logging output", + shortOption = Some("lcn") ) ) + +} diff --git a/src/main/scala/logger/LoggerOptions.scala b/src/main/scala/logger/LoggerOptions.scala new file mode 100644 index 00000000..299382f0 --- /dev/null +++ b/src/main/scala/logger/LoggerOptions.scala @@ -0,0 +1,38 @@ +// See LICENSE for license details. + +package logger + +/** Internal options used to control the logging in programs that are part of the Chisel stack + * + * @param globalLogLevel the verbosity of logging (default: [[logger.LogLevel.None]]) + * @param classLogLevels the individual verbosity of logging for specific classes + * @param logToFile if true, log to a file + * @param logClassNames indicates logging verbosity on a class-by-class basis + */ +class LoggerOptions private [logger] ( + val globalLogLevel: LogLevel.Value = LogLevelAnnotation().globalLogLevel, + val classLogLevels: Map[String, LogLevel.Value] = Map.empty, + val logClassNames: Boolean = false, + val logFileName: Option[String] = None) { + + private [logger] def copy( + globalLogLevel: LogLevel.Value = globalLogLevel, + classLogLevels: Map[String, LogLevel.Value] = classLogLevels, + logClassNames: Boolean = logClassNames, + logFileName: Option[String] = logFileName): LoggerOptions = { + + new LoggerOptions( + globalLogLevel = globalLogLevel, + classLogLevels = classLogLevels, + logClassNames = logClassNames, + logFileName = logFileName) + + } + + /** Return the name of the log file, defaults to `a.log` if unspecified */ + def getLogFileName(): Option[String] = if (!logToFile) None else logFileName.orElse(Some("a.log")) + + /** True if a [[Logger]] should be writing to a file */ + @deprecated("logToFile was removed, use logFileName.nonEmpty", "1.2") + def logToFile(): Boolean = logFileName.nonEmpty +} diff --git a/src/main/scala/logger/package.scala b/src/main/scala/logger/package.scala new file mode 100644 index 00000000..52a3331a --- /dev/null +++ b/src/main/scala/logger/package.scala @@ -0,0 +1,21 @@ +// See LICENSE for license details. + +import firrtl.AnnotationSeq +import firrtl.options.OptionsView + +package object logger { + + implicit object LoggerOptionsView extends OptionsView[LoggerOptions] { + def view(options: AnnotationSeq): LoggerOptions = options + .foldLeft(new LoggerOptions()) { (c, x) => + x match { + case LogLevelAnnotation(logLevel) => c.copy(globalLogLevel = logLevel) + case ClassLogLevelAnnotation(name, level) => c.copy(classLogLevels = c.classLogLevels + (name -> level)) + case LogFileAnnotation(f) => c.copy(logFileName = f) + case LogClassNamesAnnotation => c.copy(logClassNames = true) + case _ => c + } + } + } + +} diff --git a/src/main/scala/logger/phases/AddDefaults.scala b/src/main/scala/logger/phases/AddDefaults.scala new file mode 100644 index 00000000..f6daa811 --- /dev/null +++ b/src/main/scala/logger/phases/AddDefaults.scala @@ -0,0 +1,27 @@ +// See LICENSE for license details. + +package logger.phases + +import firrtl.AnnotationSeq +import firrtl.options.Phase + +import logger.{LoggerOption, LogLevelAnnotation} + +/** Add default logger [[Annotation]]s */ +private [logger] object AddDefaults extends Phase { + + /** Add missing default [[Logger]] [[Annotation]]s to an [[AnnotationSeq]] + * @param annotations input annotations + * @return output annotations with defaults + */ + def transform(annotations: AnnotationSeq): AnnotationSeq = { + var ll = true + annotations.collect{ case a: LoggerOption => a }.map{ + case _: LogLevelAnnotation => ll = false + case _ => + } + annotations ++ + (if (ll) Seq(LogLevelAnnotation()) else Seq() ) + } + +} diff --git a/src/main/scala/logger/phases/Checks.scala b/src/main/scala/logger/phases/Checks.scala new file mode 100644 index 00000000..c706948c --- /dev/null +++ b/src/main/scala/logger/phases/Checks.scala @@ -0,0 +1,42 @@ +// See LICENSE for license details. + +package logger.phases + +import firrtl.AnnotationSeq +import firrtl.annotations.Annotation +import firrtl.options.Phase + +import logger.{LogLevelAnnotation, LogFileAnnotation, LoggerException} + +import scala.collection.mutable + +/** Check that an [[firrtl.AnnotationSeq AnnotationSeq]] has all necessary [[firrtl.annotations.Annotation Annotation]]s + * for a [[Logger]] */ +object Checks extends Phase { + + /** Ensure that an [[firrtl.AnnotationSeq AnnotationSeq]] has necessary [[Logger]] [[firrtl.annotations.Annotation + * Annotation]]s + * @param annotations input annotations + * @return input annotations unmodified + * @throws logger.LoggerException + */ + def transform(annotations: AnnotationSeq): AnnotationSeq = { + val ll, lf = mutable.ListBuffer[Annotation]() + annotations.foreach( + _ match { + case a: LogLevelAnnotation => ll += a + case a: LogFileAnnotation => lf += a + case _ => }) + if (ll.size > 1) { + val l = ll.map{ case LogLevelAnnotation(x) => x } + throw new LoggerException( + s"""|At most one log level can be specified, but found '${l.mkString(", ")}' specified via: + | - an option or annotation: -ll, --log-level, LogLevelAnnotation""".stripMargin )} + if (lf.size > 1) { + throw new LoggerException( + s"""|At most one log file can be specified, but found ${lf.size} combinations of: + | - an options or annotation: -ltf, --log-to-file, --log-file, LogFileAnnotation""".stripMargin )} + annotations + } + +} diff --git a/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala b/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala new file mode 100644 index 00000000..77316feb --- /dev/null +++ b/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala @@ -0,0 +1,219 @@ +// See LICENSE for license details. + +package firrtl.stage.phases.tests + +import org.scalatest.{FlatSpec, Matchers, PrivateMethodTester} + +import scala.io.Source + +import java.io.File + +import firrtl._ +import firrtl.stage._ +import firrtl.stage.phases.DriverCompatibility._ + +import firrtl.options.{InputAnnotationFileAnnotation, Phase, TargetDirAnnotation} +import firrtl.stage.phases.DriverCompatibility + +class DriverCompatibilitySpec extends FlatSpec with Matchers with PrivateMethodTester { + + class PhaseFixture(val phase: Phase) + + /* This method wraps some magic that lets you use the private method DriverCompatibility.topName */ + def topName(annotations: AnnotationSeq): Option[String] = { + val topName = PrivateMethod[Option[String]]('topName) + DriverCompatibility invokePrivate topName(annotations) + } + + def simpleCircuit(main: String): String = s"""|circuit $main: + | module $main: + | node x = UInt<1>("h0") + |""".stripMargin + + /* This is a tuple holding an annotation that can be used to derive a top name and the expected top name for that + * annotation. If these annotations are presented here in the same order that DriverCompatibility.topName uses to + * discern a top name. E.g., a TopNameAnnotation is always used, even in the presence of a FirrtlSourceAnnotation. + * Note: the last two FirrtlFileAnnotations have equal precedence, but the first one in the AnnotationSeq wins. + */ + val annosWithTops = Seq( + (TopNameAnnotation("foo"), "foo"), + (FirrtlCircuitAnnotation(Parser.parse(simpleCircuit("bar"))), "bar"), + (FirrtlSourceAnnotation(simpleCircuit("baz")), "baz"), + (FirrtlFileAnnotation("src/test/resources/integration/PipeTester.fir"), "PipeTester"), + (FirrtlFileAnnotation("src/test/resources/integration/GCDTester.pb"), "GCDTester") + ) + + behavior of s"${DriverCompatibility.getClass.getName}.topName (private method)" + + /* This iterates over the tails of annosWithTops. Using the ordering of annosWithTops, if this AnnotationSeq is fed to + * DriverCompatibility.topName, the head annotation will be used to determine the top name. This test ensures that + * topName behaves as expected. + */ + for ( t <- annosWithTops.tails ) t match { + case Nil => + it should "return None on an empty AnnotationSeq" in { + topName(Seq.empty) should be (None) + } + case x => + val annotations = x.map(_._1) + val top = x.head._2 + it should s"determine a top name ('$top') from a ${annotations.head.getClass.getName}" in { + topName(annotations).get should be (top) + } + } + + def createFile(name: String): Unit = { + val file = new File(name) + file.getParentFile.getCanonicalFile.mkdirs() + file.createNewFile() + } + + behavior of classOf[AddImplicitAnnotationFile].toString + + val testDir = "test_run_dir/DriverCompatibilitySpec" + + it should "not modify the annotations if an InputAnnotationFile already exists" in + new PhaseFixture(new AddImplicitAnnotationFile) { + + createFile(testDir + "/foo.anno") + val annotations = Seq( + InputAnnotationFileAnnotation("bar.anno"), + TargetDirAnnotation(testDir), + TopNameAnnotation("foo") ) + + phase.transform(annotations).toSeq should be (annotations) + } + + it should "add an InputAnnotationFile based on a derived topName" in + new PhaseFixture(new AddImplicitAnnotationFile) { + createFile(testDir + "/bar.anno") + val annotations = Seq( + TargetDirAnnotation(testDir), + TopNameAnnotation("bar") ) + + val expected = annotations.toSet + + InputAnnotationFileAnnotation(testDir + "/bar.anno") + + phase.transform(annotations).toSet should be (expected) + } + + it should "not add an InputAnnotationFile for .anno.json annotations" in + new PhaseFixture(new AddImplicitAnnotationFile) { + createFile(testDir + "/baz.anno.json") + val annotations = Seq( + TargetDirAnnotation(testDir), + TopNameAnnotation("baz") ) + + phase.transform(annotations).toSeq should be (annotations) + } + + it should "not add an InputAnnotationFile if it cannot determine the topName" in + new PhaseFixture(new AddImplicitAnnotationFile) { + val annotations = Seq( TargetDirAnnotation(testDir) ) + + phase.transform(annotations).toSeq should be (annotations) + } + + behavior of classOf[AddImplicitFirrtlFile].toString + + it should "not modify the annotations if a CircuitOption is present" in + new PhaseFixture(new AddImplicitFirrtlFile) { + val annotations = Seq( FirrtlFileAnnotation("foo"), TopNameAnnotation("bar") ) + + phase.transform(annotations).toSeq should be (annotations) + } + + it should "add an FirrtlFileAnnotation if a TopNameAnnotation is present" in + new PhaseFixture(new AddImplicitFirrtlFile) { + val annotations = Seq( TopNameAnnotation("foo") ) + val expected = annotations.toSet + + FirrtlFileAnnotation(new File("foo.fir").getCanonicalPath) + + phase.transform(annotations).toSet should be (expected) + } + + it should "do nothing if no TopNameAnnotation is present" in + new PhaseFixture(new AddImplicitFirrtlFile) { + val annotations = Seq( TargetDirAnnotation("foo") ) + + phase.transform(annotations).toSeq should be (annotations) + } + + behavior of classOf[AddImplicitEmitter].toString + + val (nc, hfc, mfc, lfc, vc, svc) = ( new NoneCompiler, + new HighFirrtlCompiler, + new MiddleFirrtlCompiler, + new LowFirrtlCompiler, + new VerilogCompiler, + new SystemVerilogCompiler ) + + it should "convert CompilerAnnotations into EmitCircuitAnnotations without EmitOneFilePerModuleAnnotation" in + new PhaseFixture(new AddImplicitEmitter) { + val annotations = Seq( + CompilerAnnotation(nc), + CompilerAnnotation(hfc), + CompilerAnnotation(mfc), + CompilerAnnotation(lfc), + CompilerAnnotation(vc), + CompilerAnnotation(svc) + ) + val expected = annotations + .flatMap( a => Seq(a, + RunFirrtlTransformAnnotation(a.compiler.emitter), + EmitCircuitAnnotation(a.compiler.emitter.getClass)) ) + + phase.transform(annotations).toSeq should be (expected) + } + + it should "convert CompilerAnnotations into EmitAllodulesAnnotation with EmitOneFilePerModuleAnnotation" in + new PhaseFixture(new AddImplicitEmitter) { + val annotations = Seq( + EmitOneFilePerModuleAnnotation, + CompilerAnnotation(nc), + CompilerAnnotation(hfc), + CompilerAnnotation(mfc), + CompilerAnnotation(lfc), + CompilerAnnotation(vc), + CompilerAnnotation(svc) + ) + val expected = annotations + .flatMap{ + case a: CompilerAnnotation => Seq(a, + RunFirrtlTransformAnnotation(a.compiler.emitter), + EmitAllModulesAnnotation(a.compiler.emitter.getClass)) + case a => Seq(a) + } + + phase.transform(annotations).toSeq should be (expected) + } + + behavior of classOf[AddImplicitOutputFile].toString + + it should "add an OutputFileAnnotation derived from a TopNameAnnotation if no OutputFileAnnotation exists" in + new PhaseFixture(new AddImplicitOutputFile) { + val annotations = Seq( TopNameAnnotation("foo") ) + val expected = Seq( + OutputFileAnnotation("foo"), + TopNameAnnotation("foo") + ) + phase.transform(annotations).toSeq should be (expected) + } + + it should "do nothing if an OutputFileannotation already exists" in + new PhaseFixture(new AddImplicitOutputFile) { + val annotations = Seq( + TopNameAnnotation("foo"), + OutputFileAnnotation("bar") ) + val expected = annotations + phase.transform(annotations).toSeq should be (expected) + } + + it should "do nothing if no TopNameAnnotation exists" in + new PhaseFixture(new AddImplicitOutputFile) { + val annotations = Seq.empty + val expected = annotations + phase.transform(annotations).toSeq should be (expected) + } + +} diff --git a/src/test/scala/firrtlTests/DriverSpec.scala b/src/test/scala/firrtlTests/DriverSpec.scala index ae1e08e7..1cb991c8 100644 --- a/src/test/scala/firrtlTests/DriverSpec.scala +++ b/src/test/scala/firrtlTests/DriverSpec.scala @@ -206,18 +206,38 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities } // Deprecated - "Annotations can be read implicitly from the name of the circuit" in { + "Annotations can be read implicitly from the name of the circuit" - { + val input = """|circuit foo : + | module foo : + | input x : UInt<8> + | output y : UInt<8> + | y <= x""".stripMargin val top = "foo" val optionsManager = new ExecutionOptionsManager("test") with HasFirrtlOptions { commonOptions = commonOptions.copy(topName = top) + firrtlOptions = firrtlOptions.copy(firrtlSource = Some(input)) } val annoFile = new File(optionsManager.commonOptions.targetDirName, top + ".anno") - copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) - optionsManager.firrtlOptions.annotations.length should be(0) - val annos = Driver.getAnnotations(optionsManager) - annos.length should be(12) // 9 from circuit plus 3 general purpose - annos.count(_.isInstanceOf[InlineAnnotation]) should be(9) - annoFile.delete() + val vFile = new File(optionsManager.commonOptions.targetDirName, top + ".v") + "Using Driver.getAnnotations" in { + copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) + optionsManager.firrtlOptions.annotations.length should be(0) + val annos = Driver.getAnnotations(optionsManager) + annos.length should be(12) // 9 from circuit plus 3 general purpose + annos.count(_.isInstanceOf[InlineAnnotation]) should be(9) + annoFile.delete() + vFile.delete() + } + "Using Driver.execute" in { + copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) + Driver.execute(optionsManager) match { + case r: FirrtlExecutionSuccess => + val annos = r.circuitState.annotations + annos.count(_.isInstanceOf[InlineAnnotation]) should be(9) + } + annoFile.delete() + vFile.delete() + } } // Deprecated @@ -367,20 +387,29 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities "To a single file with file extension depending on the compiler by default" in { Seq( - "none" -> "./Top.fir", - "low" -> "./Top.lo.fir", - "high" -> "./Top.hi.fir", - "middle" -> "./Top.mid.fir", - "verilog" -> "./Top.v", - "mverilog" -> "./Top.v", - "sverilog" -> "./Top.sv" + "none" -> "./Foo.fir", + "low" -> "./Foo.lo.fir", + "high" -> "./Foo.hi.fir", + "middle" -> "./Foo.mid.fir", + "verilog" -> "./Foo.v", + "mverilog" -> "./Foo.v", + "sverilog" -> "./Foo.sv" ).foreach { case (compilerName, expectedOutputFileName) => + info(s"$compilerName -> $expectedOutputFileName") val manager = new ExecutionOptionsManager("test") with HasFirrtlOptions { - commonOptions = CommonOptions(topName = "Top") + commonOptions = CommonOptions(topName = "Foo") firrtlOptions = FirrtlExecutionOptions(firrtlSource = Some(input), compilerName = compilerName) } - firrtl.Driver.execute(manager) + firrtl.Driver.execute(manager) match { + case success: FirrtlExecutionSuccess => + success.emitted.size should not be (0) + success.circuitState.annotations.length should be > (0) + case a: FirrtlExecutionFailure => + fail(s"Got a FirrtlExecutionFailure! Expected FirrtlExecutionSuccess. Full message:\n${a.message}") + } + + val file = new File(expectedOutputFileName) file.exists() should be(true) @@ -397,9 +426,8 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities "mverilog" -> Seq("./Top.v", "./Child.v"), "sverilog" -> Seq("./Top.sv", "./Child.sv") ).foreach { case (compilerName, expectedOutputFileNames) => - println(s"$compilerName -> $expectedOutputFileNames") + info(s"$compilerName -> $expectedOutputFileNames") val manager = new ExecutionOptionsManager("test") with HasFirrtlOptions { - commonOptions = CommonOptions(topName = "Top") firrtlOptions = FirrtlExecutionOptions(firrtlSource = Some(input), compilerName = compilerName, emitOneFilePerModule = true) @@ -407,9 +435,10 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities firrtl.Driver.execute(manager) match { case success: FirrtlExecutionSuccess => + success.emitted.size should not be (0) success.circuitState.annotations.length should be > (0) - case _ => - + case failure: FirrtlExecutionFailure => + fail(s"Got a FirrtlExecutionFailure! Expected FirrtlExecutionSuccess. Full message:\n${failure.message}") } for (name <- expectedOutputFileNames) { diff --git a/src/test/scala/firrtlTests/FirrtlSpec.scala b/src/test/scala/firrtlTests/FirrtlSpec.scala index c3c25cd5..c110109c 100644 --- a/src/test/scala/firrtlTests/FirrtlSpec.scala +++ b/src/test/scala/firrtlTests/FirrtlSpec.scala @@ -3,6 +3,7 @@ package firrtlTests import java.io._ +import java.security.Permission import com.typesafe.scalalogging.LazyLogging @@ -320,3 +321,86 @@ abstract class CompilationTest(name: String, dir: String) extends FirrtlPropSpec compileFirrtlTest(name, dir) } } + +trait Utils { + + /** Run some Scala thunk and return STDOUT and STDERR as strings. + * @param thunk some Scala code + * @return a tuple containing STDOUT, STDERR, and what the thunk returns + */ + def grabStdOutErr[T](thunk: => T): (String, String, T) = { + val stdout, stderr = new ByteArrayOutputStream() + val ret = scala.Console.withOut(stdout) { scala.Console.withErr(stderr) { thunk } } + (stdout.toString, stderr.toString, ret) + } + + /** Encodes a System.exit exit code + * @param status the exit code + */ + private case class ExitException(status: Int) extends SecurityException(s"Found a sys.exit with code $status") + + /** A security manager that converts calls to System.exit into [[ExitException]]s by explicitly disabling the ability of + * a thread to actually exit. For more information, see: + * - https://docs.oracle.com/javase/tutorial/essential/environment/security.html + */ + private class ExceptOnExit extends SecurityManager { + override def checkPermission(perm: Permission): Unit = {} + override def checkPermission(perm: Permission, context: Object): Unit = {} + override def checkExit(status: Int): Unit = { + super.checkExit(status) + throw ExitException(status) + } + } + + /** Encodes a file that some code tries to write to + * @param the file name + */ + private case class WriteException(file: String) extends SecurityException(s"Tried to write to file $file") + + /** A security manager that converts writes to any file into [[WriteException]]s. + */ + private class ExceptOnWrite extends SecurityManager { + override def checkPermission(perm: Permission): Unit = {} + override def checkPermission(perm: Permission, context: Object): Unit = {} + override def checkWrite(file: String): Unit = { + super.checkWrite(file) + throw WriteException(file) + } + } + + /** Run some Scala code (a thunk) in an environment where all System.exit are caught and returned. This avoids a + * situation where a test results in something actually exiting and killing the entire test. This is necessary if you + * want to test a command line program, e.g., the `main` method of [[firrtl.options.Stage Stage]]. + * + * NOTE: THIS WILL NOT WORK IN SITUATIONS WHERE THE THUNK IS CATCHING ALL [[Exception]]s OR [[Throwable]]s, E.G., + * SCOPT. IF THIS IS HAPPENING THIS WILL NOT WORK. REPEAT THIS WILL NOT WORK. + * @param thunk some Scala code + * @return either the output of the thunk (`Right[T]`) or an exit code (`Left[Int]`) + */ + def catchStatus[T](thunk: => T): Either[Int, T] = { + try { + System.setSecurityManager(new ExceptOnExit()) + Right(thunk) + } catch { + case ExitException(a) => Left(a) + } finally { + System.setSecurityManager(null) + } + } + + /** Run some Scala code (a thunk) in an environment where file writes are caught and the file that a program tries to + * write to is returned. This is useful if you want to test that some thunk either tries to write to a specific file + * or doesn't try to write at all. + */ + def catchWrites[T](thunk: => T): Either[String, T] = { + try { + System.setSecurityManager(new ExceptOnWrite()) + Right(thunk) + } catch { + case WriteException(a) => Left(a) + } finally { + System.setSecurityManager(null) + } + } + +} diff --git a/src/test/scala/firrtlTests/annotationTests/TargetDirAnnotationSpec.scala b/src/test/scala/firrtlTests/annotationTests/TargetDirAnnotationSpec.scala index 8f157131..eb061d8f 100644 --- a/src/test/scala/firrtlTests/annotationTests/TargetDirAnnotationSpec.scala +++ b/src/test/scala/firrtlTests/annotationTests/TargetDirAnnotationSpec.scala @@ -5,7 +5,6 @@ package annotationTests import firrtlTests._ import firrtl._ -import firrtl.stage.TargetDirAnnotation /** Looks for [[TargetDirAnnotation]] */ class FindTargetDirTransform(expected: String) extends Transform { diff --git a/src/test/scala/firrtlTests/options/OptionParserSpec.scala b/src/test/scala/firrtlTests/options/OptionParserSpec.scala index ae4899d4..0a9ac2d5 100644 --- a/src/test/scala/firrtlTests/options/OptionParserSpec.scala +++ b/src/test/scala/firrtlTests/options/OptionParserSpec.scala @@ -4,15 +4,13 @@ package firrtlTests.options import firrtl.{AnnotationSeq, FIRRTLException} import firrtl.annotations.{Annotation, NoTargetAnnotation} -import firrtl.options.{DoNotTerminateOnExit, DuplicateHandling, OptionsException} +import firrtl.options.{DoNotTerminateOnExit, DuplicateHandling, ExceptOnError, OptionsException} import scopt.OptionParser import org.scalatest.{FlatSpec, Matchers} -import java.security.Permission - -class OptionParserSpec extends FlatSpec with Matchers { +class OptionParserSpec extends FlatSpec with Matchers with firrtlTests.Utils { case class IntAnnotation(x: Int) extends NoTargetAnnotation { def extract: Int = x @@ -32,50 +30,35 @@ class OptionParserSpec extends FlatSpec with Matchers { opt[Int]("integer").abbr("m").unbounded.action( (x, c) => IntAnnotation(x) +: c ) } - case class ExitException(status: Option[Int]) extends SecurityException("Found a sys.exit") + trait WithIntParser { val parser = new IntParser } - /* Security manager that disallows calls to sys.exit */ - class ExceptOnExit extends SecurityManager { - override def checkPermission(perm: Permission): Unit = {} - override def checkPermission(perm: Permission, context: Object): Unit = {} - override def checkExit(status: Int): Unit = { - super.checkExit(status) - throw ExitException(Some(status)) - } - } + behavior of "A default OptionsParser" - /* Tell a parser to terminate in an environment where sys.exit throws an exception */ - def catchStatus(parser: OptionParser[_], exitState: Either[String, Unit]): Option[Int] = { - System.setSecurityManager(new ExceptOnExit()) - val status = try { - parser.terminate(exitState) - throw new ExitException(None) - } catch { - case ExitException(s) => s - } - System.setSecurityManager(null) - status - } + it should "call sys.exit if terminate is called" in new WithIntParser { + info("exit status of 1 for failure") + catchStatus { parser.terminate(Left("some message")) } should be (Left(1)) - behavior of "default OptionsParser" - - it should "terminate on exit" in { - val parser = new IntParser + info("exit status of 0 for success") + catchStatus { parser.terminate(Right(Unit)) } should be (Left(0)) + } - info("By default, exit statuses are reported") - catchStatus(parser, Left("some message")) should be (Some(1)) - catchStatus(parser, Right(Unit)) should be (Some(0)) + it should "print to stderr on an invalid option" in new WithIntParser { + grabStdOutErr{ parser.parse(Array("--foo"), Seq[Annotation]()) }._2 should include ("Unknown option --foo") } - behavior of "DoNotTerminateOnExit" + behavior of "An OptionParser with DoNotTerminateOnExit mixed in" it should "disable sys.exit for terminate method" in { val parser = new IntParser with DoNotTerminateOnExit - catchStatus(parser, Left("some message")) should be (None) - catchStatus(parser, Right(Unit)) should be (None) + + info("no exit for failure") + catchStatus { parser.terminate(Left("some message")) } should be (Right(())) + + info("no exit for success") + catchStatus { parser.terminate(Right(Unit)) } should be (Right(())) } - behavior of "DuplicateHandling" + behavior of "An OptionParser with DuplicateHandling mixed in" it should "detect short duplicates" in { val parser = new IntParser with DuplicateHandling with DuplicateShortOption @@ -89,4 +72,12 @@ class OptionParserSpec extends FlatSpec with Matchers { .getMessage should startWith ("Duplicate long option") } + behavior of "An OptionParser with ExceptOnError mixed in" + + it should "cause an OptionsException on an invalid option" in { + val parser = new IntParser with ExceptOnError + intercept[OptionsException] { parser.parse(Array("--foo"), Seq[Annotation]()) } + .getMessage should include ("Unknown option") + } + } diff --git a/src/test/scala/firrtlTests/options/RegistrationSpec.scala b/src/test/scala/firrtlTests/options/RegistrationSpec.scala index c060341d..43d71b6d 100644 --- a/src/test/scala/firrtlTests/options/RegistrationSpec.scala +++ b/src/test/scala/firrtlTests/options/RegistrationSpec.scala @@ -6,7 +6,7 @@ import org.scalatest.{FlatSpec, Matchers} import scopt.OptionParser import java.util.ServiceLoader -import firrtl.options.{RegisteredTransform, RegisteredLibrary} +import firrtl.options.{RegisteredTransform, RegisteredLibrary, ShellOption} import firrtl.passes.Pass import firrtl.ir.Circuit import firrtl.annotations.NoTargetAnnotation @@ -16,16 +16,23 @@ case object HelloAnnotation extends NoTargetAnnotation class FooTransform extends Pass with RegisteredTransform { def run(c: Circuit): Circuit = c - def addOptions(p: OptionParser[AnnotationSeq]): Unit = - p.opt[Unit]("hello") - .action( (_, c) => HelloAnnotation +: c ) + + val options = Seq( + new ShellOption[Unit]( + longOption = "hello", + toAnnotationSeq = _ => Seq(HelloAnnotation), + helpText = "Hello option") ) + } class BarLibrary extends RegisteredLibrary { def name: String = "Bar" - def addOptions(p: OptionParser[AnnotationSeq]): Unit = - p.opt[Unit]("world") - .action( (_, c) => HelloAnnotation +: c ) + + val options = Seq( + new ShellOption[Unit]( + longOption = "world", + toAnnotationSeq = _ => Seq(HelloAnnotation), + helpText = "World option") ) } class RegistrationSpec extends FlatSpec with Matchers { diff --git a/src/test/scala/firrtlTests/options/ShellSpec.scala b/src/test/scala/firrtlTests/options/ShellSpec.scala index d87a9a30..50000550 100644 --- a/src/test/scala/firrtlTests/options/ShellSpec.scala +++ b/src/test/scala/firrtlTests/options/ShellSpec.scala @@ -2,12 +2,24 @@ package firrtlTests.options -import org.scalatest._ +import org.scalatest.{FlatSpec, Matchers} +import firrtl.annotations.NoTargetAnnotation import firrtl.options.Shell class ShellSpec extends FlatSpec with Matchers { + case object A extends NoTargetAnnotation + case object B extends NoTargetAnnotation + case object C extends NoTargetAnnotation + case object D extends NoTargetAnnotation + case object E extends NoTargetAnnotation + + trait AlphabeticalCli { this: Shell => + parser.opt[Unit]('c', "c-option").unbounded().action( (x, c) => C +: c ) + parser.opt[Unit]('d', "d-option").unbounded().action( (x, c) => D +: c ) + parser.opt[Unit]('e', "e-option").unbounded().action( (x, c) => E +: c ) } + behavior of "Shell" it should "detect all registered libraries and transforms" in { @@ -19,4 +31,10 @@ class ShellSpec extends FlatSpec with Matchers { info("Found BarLibrary") shell.registeredLibraries.map(_.getClass.getName) should contain ("firrtlTests.options.BarLibrary") } + + it should "correctly order annotations and options" in { + val shell = new Shell("foo") with AlphabeticalCli + + shell.parse(Array("-c", "-d", "-e"), Seq(A, B)).toSeq should be (Seq(A, B, C, D, E)) + } } diff --git a/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala b/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala new file mode 100644 index 00000000..b988f838 --- /dev/null +++ b/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala @@ -0,0 +1,48 @@ +// See LICENSE for license details. + +package firrtlTests.options.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.AnnotationSeq +import firrtl.options.{OptionsException, OutputAnnotationFileAnnotation, Phase, TargetDirAnnotation} +import firrtl.options.phases.Checks + +class ChecksSpec extends FlatSpec with Matchers { + + val targetDir = TargetDirAnnotation("foo") + val annoOut = OutputAnnotationFileAnnotation("bar") + + class Fixture { val phase: Phase = new Checks } + + /* A minimum annotation Seq that will pass [[Checks]] */ + val min = Seq(targetDir) + + def checkExceptionMessage(phase: Phase, annotations: AnnotationSeq, messageStart: String): Unit = + intercept[OptionsException]{ phase.transform(annotations) }.getMessage should startWith(messageStart) + + behavior of classOf[Checks].toString + + it should "enforce exactly one TargetDirAnnotation" in new Fixture { + info("0 target directories throws an exception") + checkExceptionMessage(phase, Seq.empty, "Exactly one target directory must be specified") + + info("2 target directories throws an exception") + checkExceptionMessage(phase, Seq(targetDir, targetDir), "Exactly one target directory must be specified") + } + + it should "enforce zero or one output annotation files" in new Fixture { + info("0 output annotation files is okay") + phase.transform(Seq(targetDir)) + + info("2 output annotation files throws an exception") + val in = Seq(targetDir, annoOut, annoOut) + checkExceptionMessage(phase, in, "At most one output annotation file can be specified") + } + + it should "pass if the minimum annotations are specified" in new Fixture { + info(s"""Minimum required: ${min.map(_.getClass.getSimpleName).mkString(", ")}""") + phase.transform(min) + } + +} diff --git a/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala b/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala new file mode 100644 index 00000000..1007ca8c --- /dev/null +++ b/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala @@ -0,0 +1,95 @@ +// See LICENSE for license details. + +package firrtlTests.options.phases + +import org.scalatest.{FlatSpec, Matchers} + +import java.io.{File, PrintWriter} + +import firrtl.AnnotationSeq +import firrtl.annotations.{Annotation, AnnotationFileNotFoundException, JsonProtocol, + NoTargetAnnotation} +import firrtl.options.phases.GetIncludes +import firrtl.options.{InputAnnotationFileAnnotation, Phase} +import firrtl.util.BackendCompilationUtilities + +case object A extends NoTargetAnnotation +case object B extends NoTargetAnnotation +case object C extends NoTargetAnnotation +case object D extends NoTargetAnnotation +case object E extends NoTargetAnnotation + +class GetIncludesSpec extends FlatSpec with Matchers with BackendCompilationUtilities with firrtlTests.Utils { + + val dir = new File("test_run_dir/GetIncludesSpec") + dir.mkdirs() + + def ref(filename: String): InputAnnotationFileAnnotation = InputAnnotationFileAnnotation(s"$dir/$filename.anno.json") + + def checkAnnos(a: AnnotationSeq, b: AnnotationSeq): Unit = { + info("read the expected number of annotations") + a.size should be (b.size) + + info("annotations match exact order") + a.zip(b).foreach{ case (ax, bx) => ax should be (bx) } + } + + val files = Seq( + new File(dir + "/a.anno.json") -> Seq(A, ref("b")), + new File(dir + "/b.anno.json") -> Seq(B, ref("c"), ref("a")), + new File(dir + "/c.anno.json") -> Seq(C, ref("d"), ref("e")), + new File(dir + "/d.anno.json") -> Seq(D), + new File(dir + "/e.anno.json") -> Seq(E) + ) + + files.foreach{ case (file, annotations) => + val pw = new PrintWriter(file) + pw.write(JsonProtocol.serialize(annotations)) + pw.close() + } + + class Fixture { val phase: Phase = new GetIncludes } + + behavior of classOf[GetIncludes].toString + + it should "throw an exception if the annotation file doesn't exit" in new Fixture { + intercept[AnnotationFileNotFoundException]{ phase.transform(Seq(ref("f"))) } + .getMessage should startWith("Annotation file") + } + + it should "read annotations from a file" in new Fixture { + val e = ref("e") + val in = Seq(e) + val expect = Seq(E) + val out = phase.transform(in) + + checkAnnos(out, expect) + } + + it should "read annotations from multiple files, but not reading duplicates" in new Fixture { + val Seq(d, e) = Seq("d", "e").map(ref) + val in = Seq(d, e, e, d) + val expect = Seq(D, E) + val (stdout, _, out) = grabStdOutErr { phase.transform(in) } + + checkAnnos(out, expect) + + Seq("d", "e").foreach{ x => + info(s"a warning about '$x.anno.json' was printed") + stdout should include (s"Warning: Annotation file ($dir/$x.anno.json) already included!") + } + } + + it should "handle recursive references gracefully, but show a warning" in new Fixture { + val Seq(a, b, c, d, e) = Seq("a", "b", "c", "d", "e").map(ref) + val in = Seq(a) + val expect = Seq(A, B, C, D, E) + val (stdout, _, out) = grabStdOutErr { phase.transform(in) } + + checkAnnos(out, expect) + + info("a warning about 'a.anno.json' was printed") + stdout should include (s"Warning: Annotation file ($dir/a.anno.json)") + } + +} diff --git a/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala b/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala new file mode 100644 index 00000000..07aad53f --- /dev/null +++ b/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala @@ -0,0 +1,108 @@ +// See LICENSE for license details. + +package firrtlTests.options.phases + +import org.scalatest.{FlatSpec, Matchers} + +import java.io.File + +import firrtl.{AnnotationSeq, EmittedFirrtlCircuitAnnotation, EmittedFirrtlCircuit} +import firrtl.annotations.{DeletedAnnotation, NoTargetAnnotation} +import firrtl.options.{InputAnnotationFileAnnotation, OutputAnnotationFileAnnotation, Phase, WriteDeletedAnnotation} +import firrtl.options.phases.{GetIncludes, WriteOutputAnnotations} +import firrtl.stage.FirrtlFileAnnotation + +class WriteOutputAnnotationsSpec extends FlatSpec with Matchers with firrtlTests.Utils { + + val dir = "test_run_dir/WriteOutputAnnotationSpec" + + /** Check if the annotations contained by a [[File]] and the same, and in the same order, as a reference + * [[AnnotationSeq]]. This uses [[GetIncludes]] as that already knows how to read annotation files. + * @param f a file to read + * @param a the expected annotations + */ + private def fileContainsAnnotations(f: File, a: AnnotationSeq): Unit = { + info(s"output file '$f' exists") + f should (exist) + + info(s"reading '$f' works") + val read = (new GetIncludes) + .transform(Seq(InputAnnotationFileAnnotation(f.toString))) + .filterNot{ + case a @ DeletedAnnotation(_, _: InputAnnotationFileAnnotation) => true + case _ => false } + + info(s"annotations in file are expected size") + read.size should be (a.size) + + read + .zip(a) + .foreach{ case (read, expected) => + info(s"$read matches") + read should be (expected) } + + f.delete() + } + + class Fixture { val phase: Phase = new WriteOutputAnnotations } + + behavior of classOf[WriteOutputAnnotations].toString + + it should "write annotations to a file (excluding DeletedAnnotations)" in new Fixture { + val file = new File(dir + "/should-write-annotations-to-a-file.anno.json") + val annotations = Seq( OutputAnnotationFileAnnotation(file.toString), + WriteOutputAnnotationsSpec.FooAnnotation, + WriteOutputAnnotationsSpec.BarAnnotation(0), + WriteOutputAnnotationsSpec.BarAnnotation(1), + DeletedAnnotation("foo", WriteOutputAnnotationsSpec.FooAnnotation) ) + val expected = annotations.filter { + case a: DeletedAnnotation => false + case a => true + } + val out = phase.transform(annotations) + + info("annotations are unmodified") + out.toSeq should be (annotations) + + fileContainsAnnotations(file, expected) + } + + it should "include DeletedAnnotations if a WriteDeletedAnnotation is present" in new Fixture { + val file = new File(dir + "should-include-deleted.anno.json") + val annotations = Seq( OutputAnnotationFileAnnotation(file.toString), + WriteOutputAnnotationsSpec.FooAnnotation, + WriteOutputAnnotationsSpec.BarAnnotation(0), + WriteOutputAnnotationsSpec.BarAnnotation(1), + DeletedAnnotation("foo", WriteOutputAnnotationsSpec.FooAnnotation), + WriteDeletedAnnotation ) + val out = phase.transform(annotations) + + info("annotations are unmodified") + out.toSeq should be (annotations) + + fileContainsAnnotations(file, annotations) + } + + it should "do nothing if no output annotation file is specified" in new Fixture { + val annotations = Seq( WriteOutputAnnotationsSpec.FooAnnotation, + WriteOutputAnnotationsSpec.BarAnnotation(0), + WriteOutputAnnotationsSpec.BarAnnotation(1) ) + + val out = catchWrites { phase.transform(annotations) } match { + case Right(a) => + info("no file writes occurred") + a + case Left(a) => + fail(s"No file writes expected, but a write to '$a' ocurred!") + } + + info("annotations are unmodified") + out.toSeq should be (annotations) + } + +} + +private object WriteOutputAnnotationsSpec { + case object FooAnnotation extends NoTargetAnnotation + case class BarAnnotation(x: Int) extends NoTargetAnnotation +} diff --git a/src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala new file mode 100644 index 00000000..f13a498f --- /dev/null +++ b/src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala @@ -0,0 +1,34 @@ +// See LICENSE for license details. + +package firrtlTests.stage + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.stage.RunFirrtlTransformAnnotation +import firrtl.options.Shell +import firrtl.stage.FirrtlCli + +class FirrtlCliSpec extends FlatSpec with Matchers { + + behavior of "FirrtlCli for RunFirrtlTransformAnnotation / -fct / --custom-transforms" + + it should "preserver transform order" in { + val shell = new Shell("foo") with FirrtlCli + val args = Array( + "--custom-transforms", "firrtl.transforms.BlackBoxSourceHelper,firrtl.transforms.CheckCombLoops", + "--custom-transforms", "firrtl.transforms.CombineCats", + "--custom-transforms", "firrtl.transforms.ConstantPropagation" ) + val expected = Seq( + classOf[firrtl.transforms.BlackBoxSourceHelper], + classOf[firrtl.transforms.CheckCombLoops], + classOf[firrtl.transforms.CombineCats], + classOf[firrtl.transforms.ConstantPropagation] ) + + shell + .parse(args) + .collect{ case a: RunFirrtlTransformAnnotation => a } + .zip(expected) + .map{ case (RunFirrtlTransformAnnotation(a), b) => a.getClass should be (b) } + } + +} diff --git a/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala new file mode 100644 index 00000000..ca766d52 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala @@ -0,0 +1,421 @@ +// See LICENSE for license details. + +package firrtlTests.stage + +import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers} + +import java.io.{File, PrintWriter} + +import scala.io.Source + +import firrtl.stage.FirrtlMain +import firrtl.util.BackendCompilationUtilities + +/** Testing for the top-level [[FirrtlStage]] via [[FirrtlMain]]. + * + * This test uses the [[org.scalatest.FeatureSpec FeatureSpec]] intentionally as this test exercises the top-level + * interface and is more suitable to an Acceptance Testing style. + */ +class FirrtlMainSpec extends FeatureSpec with GivenWhenThen with Matchers with firrtlTests.Utils + with BackendCompilationUtilities { + + /** Parameterizes one test of [[FirrtlMain]]. Running the [[FirrtlMain]] `main` with certain args should produce + * certain files. + * @param args arguments to pass + * @param circuit a [[FirrtlCircuitFixture]] to use. This will generate an appropriate '-i $targetDir/$main.fi' + * argument. + * @param files expected files that will be created + * @param stdout expected stdout string, None if no output expected + * @param stderr expected stderr string, None if no output expected + * @param result expected exit code + */ + case class FirrtlMainTest( + args: Array[String], + circuit: Option[FirrtlCircuitFixture] = Some(new SimpleFirrtlCircuitFixture), + files: Seq[String] = Seq.empty, + stdout: Option[String] = None, + stderr: Option[String] = None, + result: Int = 0) { + /** Generate a name for the test based on the arguments */ + def testName: String = "args" + args.mkString("_") + + /** Print the arguments as a single string */ + def argsString: String = args.mkString(" ") + } + + /** Run the FIRRTL stage with some command line arguments expecting output files to be created. The target directory is + * implied, but will be created by the stage. + * @param p some test parameters + */ + def runStageExpectFiles(p: FirrtlMainTest): Unit = { + scenario(s"""User runs FIRRTL Stage with '${p.argsString}'""") { + val f = new FirrtlMainFixture + val td = new TargetDirectoryFixture(p.testName) + + val inputFile: Array[String] = p.circuit match { + case Some(c) => + And("some input FIRRTL IR") + val in = new File(td.dir, c.main) + val pw = new PrintWriter(in) + pw.write(c.input) + pw.close() + Array("-i", in.toString) + case None => Array.empty + } + + p.files.foreach( f => new File(td.buildDir + s"/$f").delete() ) + + When(s"""the user tries to compile with '${p.argsString}'""") + val (stdout, stderr, result) = + grabStdOutErr { catchStatus { f.stage.main(inputFile ++ Array("-td", td.buildDir.toString) ++ p.args) } } + + p.stdout match { + case Some(a) => + Then(s"""STDOUT should include "$a"""") + stdout should include (a) + case None => + Then(s"nothing should print to STDOUT") + stdout should be (empty) + } + + p.stderr match { + case Some(a) => + And(s"""STDERR should include "$a"""") + stderr should include (a) + case None => + And(s"nothing should print to STDERR") + stderr should be (empty) + } + + p.result match { + case 0 => + And(s"the exit code should be 0") + result shouldBe a [Right[_,_]] + case a => + And(s"the exit code should be $a") + result shouldBe (Left(a)) + } + + p.files.foreach { f => + And(s"file '$f' should be emitted in the target directory") + val out = new File(td.buildDir + s"/$f") + out should (exist) + } + } + } + + + /** Test fixture that links to the [[FirrtlMain]] object. This could be done without, but its use matches the + * Given/When/Then style more accurately. + */ + class FirrtlMainFixture { + Given("the FIRRTL stage") + val stage = FirrtlMain + } + + /** Test fixture that creates a build directory + * @param dirName the name of the base directory; a `build` directory is created under this + */ + class TargetDirectoryFixture(dirName: String) { + val dir = new File(s"test_run_dir/FirrtlMainSpec/$dirName") + val buildDir = new File(dir + "/build") + dir.mkdirs() + } + + trait FirrtlCircuitFixture { + val main: String + val input: String + } + + /** Test fixture defining a simple FIRRTL circuit that will emit differently with and without `--split-modules`. */ + class SimpleFirrtlCircuitFixture extends FirrtlCircuitFixture { + val main: String = "Top" + val input: String = + """|circuit Top: + | module Top: + | output foo: UInt<32> + | inst c of Child + | inst e of External + | foo <= tail(add(c.foo, e.foo), 1) + | module Child: + | output foo: UInt<32> + | inst e of External + | foo <= e.foo + | extmodule External: + | output foo: UInt<32> + |""".stripMargin + } + + info("As a FIRRTL command line user") + info("I want to compile some FIRRTL") + feature("FirrtlMain command line interface") { + scenario("User tries to discover available options") { + val f = new FirrtlMainFixture + + When("the user passes '--help'") + /* Note: THIS CANNOT CATCH THE STATUS BECAUSE SCOPT CATCHES ALL THROWABLE!!! The catchStatus is only used to prevent + * sys.exit from killing the test. However, this should additionally return an exit code of 0 and not print an + * error. The nature of running this through catchStatus causes scopt to intercept the custom SecurityException + * and then use that as evidence to exit with a code of 1. + */ + val (out, _, result) = grabStdOutErr { catchStatus { f.stage.main(Array("--help")) } } + + Then("the usage text should be shown") + out should include ("Usage: firrtl") + + And("usage text should show known registered transforms") + out should include ("--no-dce") + + And("usage text should show known registered libraries") + out should include ("MemLib Options") + + info("""And the exit code should be 0, but scopt catches all throwable, so we can't check this... ¯\_(ツ)_/¯""") + // And("the exit code should be zero") + // out should be (Left(0)) + } + + Seq( + /* Test all standard emitters with and without annotation file outputs */ + FirrtlMainTest(args = Array("-X", "none", "-E", "chirrtl"), + files = Seq("Top.fir")), + FirrtlMainTest(args = Array("-X", "high", "-E", "high"), + files = Seq("Top.hi.fir")), + FirrtlMainTest(args = Array("-X", "middle", "-E", "middle", "-foaf", "Top"), + files = Seq("Top.mid.fir", "Top.anno.json")), + FirrtlMainTest(args = Array("-X", "low", "-E", "low", "-foaf", "annotations.anno.json"), + files = Seq("Top.lo.fir", "annotations.anno.json")), + FirrtlMainTest(args = Array("-X", "verilog", "-E", "verilog", "-foaf", "foo.anno"), + files = Seq("Top.v", "foo.anno.anno.json")), + FirrtlMainTest(args = Array("-X", "sverilog", "-E", "sverilog", "-foaf", "foo.json"), + files = Seq("Top.sv", "foo.json.anno.json"), + stdout = Some("SystemVerilog Compiler behaves the same as the Verilog Compiler!")), + + /* Test all one file per module emitters */ + FirrtlMainTest(args = Array("-X", "none", "-e", "chirrtl"), + files = Seq("Top.fir", "Child.fir")), + FirrtlMainTest(args = Array("-X", "high", "-e", "high"), + files = Seq("Top.hi.fir", "Child.hi.fir")), + FirrtlMainTest(args = Array("-X", "middle", "-e", "middle"), + files = Seq("Top.mid.fir", "Child.mid.fir")), + FirrtlMainTest(args = Array("-X", "low", "-e", "low"), + files = Seq("Top.lo.fir", "Child.lo.fir")), + FirrtlMainTest(args = Array("-X", "verilog", "-e", "verilog"), + files = Seq("Top.v", "Child.v")), + FirrtlMainTest(args = Array("-X", "sverilog", "-e", "sverilog"), + files = Seq("Top.sv", "Child.sv"), + stdout = Some("SystemVerilog Compiler behaves the same as the Verilog Compiler!")), + + /* Test changes to output file name */ + FirrtlMainTest(args = Array("-X", "none", "-E", "chirrtl", "-o", "foo"), + files = Seq("foo.fir")), + FirrtlMainTest(args = Array("-X", "high", "-E", "high", "-o", "foo"), + files = Seq("foo.hi.fir")), + FirrtlMainTest(args = Array("-X", "middle", "-E", "middle", "-o", "foo.middle"), + files = Seq("foo.middle.mid.fir")), + FirrtlMainTest(args = Array("-X", "low", "-E", "low", "-o", "foo.lo.fir"), + files = Seq("foo.lo.fir")), + FirrtlMainTest(args = Array("-X", "verilog", "-E", "verilog", "-o", "foo.sv"), + files = Seq("foo.sv.v")), + FirrtlMainTest(args = Array("-X", "sverilog", "-E", "sverilog", "-o", "Foo"), + files = Seq("Foo.sv"), + stdout = Some("SystemVerilog Compiler behaves the same as the Verilog Compiler!")) + ) + .foreach(runStageExpectFiles) + + scenario("User doesn't specify a target directory") { + val f = new FirrtlMainFixture + + When("the user doesn't specify a target directory") + val outName = "FirrtlMainSpecNoTargetDirectory" + val out = new File(s"$outName.hi.fir") + out.delete() + val result = catchStatus { + f.stage.main(Array("-i", "src/test/resources/integration/GCDTester.fir", "-o", outName, "-X", "high", + "-E", "high")) } + + Then("outputs should be written to current directory") + out should (exist) + out.delete() + } + + scenario("User provides Protocol Buffer input") { + val f = new FirrtlMainFixture + val td = new TargetDirectoryFixture("protobuf-works") + + And("some Protocol Buffer input") + val protobufIn = new File(td.dir + "/Foo.pb") + copyResourceToFile("/integration/GCDTester.pb", protobufIn) + + When("the user tries to compile to High FIRRTL") + f.stage.main(Array("-i", protobufIn.toString, "-X", "high", "-E", "high", "-td", td.buildDir.toString, + "-o", "Foo")) + + Then("the output should be the same as using FIRRTL input") + new File(td.buildDir + "/Foo.hi.fir") should (exist) + } + + } + + info("As a FIRRTL command line user") + info("I want to receive error messages when I do not specify mandatory inputs") + feature("FirrtlMain input validation of mandatory options") { + scenario("User gives no command line options (no input circuit specified)") { + val f = new FirrtlMainFixture + + When("the user passes no arguments") + val (out, err, result) = grabStdOutErr { catchStatus { f.stage.main(Array.empty) } } + + Then("an error should be printed on stdout") + out should include (s"Error: Unable to determine FIRRTL source to read") + + And("no usage text should be shown") + out should not include ("Usage: firrtl") + + And("nothing should print to stderr") + err should be (empty) + + And("the exit code should be 1") + result should be (Left(1)) + } + } + + info("As a FIRRTL command line user") + info("I want to receive helpful error and warnings message") + feature("FirrtlMain input validation") { + /* Note: most input validation occurs inside firrtl.stage.phases.Checks. This seeks to validate command line + * behavior. + */ + + scenario("User tries to use an implicit annotation file") { + val f = new FirrtlMainFixture + val td = new TargetDirectoryFixture("implict-annotation-file") + val circuit = new SimpleFirrtlCircuitFixture + + And("implicit legacy and extant annotation files") + val annoFiles = Array( (new File(td.dir + "/Top.anno"), "/annotations/SampleAnnotations.anno"), + (new File(td.dir + "/Top.anno.json"), "/annotations/SampleAnnotations.anno.json") ) + annoFiles.foreach{ case (file, source) => copyResourceToFile(source, file) } + + When("the user implies an annotation file (an annotation file has the same base name as an input file)") + val in = new File(td.dir + "/Top.fir") + val pw = new PrintWriter(in) + pw.write(circuit.input) + pw.close() + val (out, _, result) = grabStdOutErr{ catchStatus { f.stage.main(Array("-td", td.dir.toString, + "-i", in.toString, + "-foaf", "Top.out", + "-X", "high", + "-E", "high")) } } + + Then("the implicit annotation file should NOT be read") + val annoFileOut = new File(td.dir + "/Top.out.anno.json") + val annotationJson = Source.fromFile(annoFileOut).mkString + annotationJson should not include ("InlineInstances") + + And("no warning should be printed") + out should not include ("Warning:") + + And("no error should be printed") + out should not include ("Error:") + + And("the exit code should be 0") + result shouldBe a [Right[_,_]] + } + + scenario("User provides unsupported legacy annotations") { + val f = new FirrtlMainFixture + val td = new TargetDirectoryFixture("legacy-annotation-file") + val circuit = new SimpleFirrtlCircuitFixture + + And("a legacy annotation file") + val annoFile = new File(td.dir + "/legacy.anno") + copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) + + When("the user provides legacy annotations") + val in = new File(td.dir + "/Top.fir") + val pw = new PrintWriter(in) + pw.write(circuit.input) + pw.close() + val (out, _, result) = grabStdOutErr{ catchStatus { f.stage.main(Array("-td", td.dir.toString, + "-i", in.toString, + "-faf", annoFile.toString, + "-foaf", "Top", + "-X", "high")) } } + + Then("a warning should be printed") + out should include ("YAML Annotation files are deprecated") + + And("the exit code should be 0") + result shouldBe a [Right[_,_]] + } + + Seq( + /* Erroneous inputs */ + FirrtlMainTest(args = Array("--thisIsNotASupportedOption"), + circuit = None, + stdout = Some("Error: Unknown option"), + result = 1), + FirrtlMainTest(args = Array("-i", "foo", "--info-mode", "Use"), + circuit = None, + stdout = Some("Unknown info mode 'Use'! (Did you misspell it?)"), + result = 1), + FirrtlMainTest(args = Array("-i", "test_run_dir/I-DO-NOT-EXIST"), + circuit = None, + stdout = Some("Input file 'test_run_dir/I-DO-NOT-EXIST' not found!"), + result = 1), + FirrtlMainTest(args = Array("-i", "foo", "-X", "Verilog"), + circuit = None, + stdout = Some("Unknown compiler name 'Verilog'! (Did you misspell it?)"), + result = 1) + ) + .foreach(runStageExpectFiles) + + } + + info("As a FIRRTL transform developer") + info("I want to register my custom transforms with FIRRTL") + feature("FirrtlMain transform registration") { + scenario("User doesn't know if their transforms were registered") { + val f = new FirrtlMainFixture + + When("the user passes '--show-registrations'") + val (out, _, result) = grabStdOutErr { catchStatus { f.stage.main(Array("--show-registrations")) } } + + Then("stdout should show registered transforms") + out should include ("firrtl.passes.InlineInstances") + + And("stdout should show registered libraries") + out should include("firrtl.passes.memlib.MemLibOptions") + + And("the exit code should be 1") + result should be (Left(1)) + } + } + + info("As a longtime FIRRTL user") + info("I migrate from Driver to FirrtlMain") + feature("FirrtlMain migration helpers") { + def optionRemoved(a: String): Option[String] = Some(s"Option '$a' was removed as part of the FIRRTL Stage refactor") + Seq( + /* Removed --top-name/-tn handling */ + FirrtlMainTest(args = Array("--top-name", "foo"), + circuit = None, + stdout = optionRemoved("--top-name/-tn"), + result = 1), + FirrtlMainTest(args = Array("-tn"), + circuit = None, + stdout = optionRemoved("--top-name/-tn"), + result = 1), + /* Removed --split-modules/-fsm handling */ + FirrtlMainTest(args = Array("--split-modules"), + circuit = None, + stdout = optionRemoved("--split-modules/-fsm"), + result = 1), + FirrtlMainTest(args = Array("-fsm"), + circuit = None, + stdout = optionRemoved("--split-modules/-fsm"), + result = 1) + ) + .foreach(runStageExpectFiles) + } +} diff --git a/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala new file mode 100644 index 00000000..cf8bfb26 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala @@ -0,0 +1,70 @@ +// See LICENSE for license details. + +package firrtlTests.stage + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.options._ +import firrtl.stage._ + +import firrtl.{CircuitForm, CircuitState, ir, NoneCompiler, Parser, UnknownForm} +import firrtl.options.Viewer.view +import firrtl.stage.{FirrtlOptions, FirrtlOptionsView} + +class BazCompiler extends NoneCompiler + +class Baz_Compiler extends NoneCompiler + +class FirrtlOptionsViewSpec extends FlatSpec with Matchers { + + behavior of FirrtlOptionsView.getClass.getName + + def circuitString(main: String): String = s"""|circuit $main: + | module $main: + | node x = UInt<1>("h0") + |""".stripMargin + + val corge: String = circuitString("corge") + + val grault: ir.Circuit = Parser.parse(circuitString("grault")) + + val annotations = Seq( + /* FirrtlOptions */ + OutputFileAnnotation("bar"), + CompilerAnnotation(new BazCompiler()), + InfoModeAnnotation("use"), + FirrtlCircuitAnnotation(grault) + ) + + it should "construct a view from an AnnotationSeq" in { + val out = view[FirrtlOptions](annotations) + + out.outputFileName should be (Some("bar")) + out.compiler.getClass should be (classOf[BazCompiler]) + out.infoModeName should be ("use") + out.firrtlCircuit should be (Some(grault)) + } + + /* This test only exists to catch changes to existing behavior. This test does not indicate that this is the correct + * behavior, only that modifications to existing code will not change behavior that people may expect. + */ + it should "overwrite or append to earlier annotation information with later annotation information" in { + val corge_ = circuitString("xyzzy_") + val grault_ = Parser.parse(circuitString("thud_")) + + val overwrites = Seq( + OutputFileAnnotation("bar_"), + CompilerAnnotation(new Baz_Compiler()), + InfoModeAnnotation("gen"), + FirrtlCircuitAnnotation(grault_) + ) + + val out = view[FirrtlOptions](annotations ++ overwrites) + + out.outputFileName should be (Some("bar_")) + out.compiler.getClass should be (classOf[Baz_Compiler]) + out.infoModeName should be ("gen") + out.firrtlCircuit should be (Some(grault_)) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala new file mode 100644 index 00000000..a86c5f12 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala @@ -0,0 +1,101 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.{AnnotationSeq, Parser} +import firrtl.annotations.NoTargetAnnotation +import firrtl.options.{OptionsException, Phase, PhasePrerequisiteException} +import firrtl.stage.{CircuitOption, FirrtlCircuitAnnotation, FirrtlSourceAnnotation, InfoModeAnnotation, + FirrtlFileAnnotation} +import firrtl.stage.phases.AddCircuit + +import java.io.{File, FileWriter} + +class AddCircuitSpec extends FlatSpec with Matchers { + + case class FooAnnotation(x: Int) extends NoTargetAnnotation + case class BarAnnotation(x: String) extends NoTargetAnnotation + + class Fixture { val phase: Phase = new AddCircuit } + + behavior of classOf[AddCircuit].toString + + def firrtlSource(name: String): String = + s"""|circuit $name: + | module $name: + | input a: UInt<1> + | output b: UInt<1> + | b <= not(a) + |""".stripMargin + + it should "throw a PhasePrerequisiteException if a CircuitOption exists without an InfoModeAnnotation" in + new Fixture { + {the [PhasePrerequisiteException] thrownBy phase.transform(Seq(FirrtlSourceAnnotation("foo")))} + .message should startWith ("An InfoModeAnnotation must be present") + } + + it should "do nothing if no CircuitOption annotations are present" in new Fixture { + val annotations = (1 to 10).map(FooAnnotation) ++ + ('a' to 'm').map(_.toString).map(BarAnnotation) :+ InfoModeAnnotation("ignore") + phase.transform(annotations).toSeq should be (annotations.toSeq) + } + + val (file, fileCircuit) = { + val source = firrtlSource("foo") + val fileName = "test_run_dir/AddCircuitSpec.fir" + val fw = new FileWriter(new File(fileName)) + fw.write(source) + fw.close() + (fileName, Parser.parse(source)) + } + + val (source, sourceCircuit) = { + val source = firrtlSource("bar") + (source, Parser.parse(source)) + } + + it should "transform and remove CircuitOption annotations" in new Fixture { + val circuit = Parser.parse(firrtlSource("baz")) + + val annotations = Seq( + FirrtlFileAnnotation(file), + FirrtlSourceAnnotation(source), + FirrtlCircuitAnnotation(circuit), + InfoModeAnnotation("ignore") ) + + val annotationsExpected = Set( + FirrtlCircuitAnnotation(fileCircuit), + FirrtlCircuitAnnotation(sourceCircuit), + FirrtlCircuitAnnotation(circuit) ) + + val out = phase.transform(annotations).toSeq + + info("generated expected FirrtlCircuitAnnotations") + out.collect{ case a: FirrtlCircuitAnnotation => a}.toSet should be (annotationsExpected) + + info("all CircuitOptions were removed") + out.collect{ case a: CircuitOption => a } should be (empty) + } + + it should """add info for a FirrtlFileAnnotation with a "gen" info mode""" in new Fixture { + phase.transform(Seq(InfoModeAnnotation("gen"), FirrtlFileAnnotation(file))) + .collectFirst{ case a: FirrtlCircuitAnnotation => a.circuit.serialize } + .get should include ("AddCircuitSpec") + } + + it should """add info for a FirrtlSourceAnnotation with an "append" info mode""" in new Fixture { + phase.transform(Seq(InfoModeAnnotation("append"), FirrtlSourceAnnotation(source))) + .collectFirst{ case a: FirrtlCircuitAnnotation => a.circuit.serialize } + .get should include ("anonymous source") + } + + it should "throw an OptionsException if the specified file doesn't exist" in new Fixture { + val a = Seq(InfoModeAnnotation("ignore"), FirrtlFileAnnotation("test_run_dir/I-DO-NOT-EXIST")) + + {the [OptionsException] thrownBy phase.transform(a)} + .message should startWith (s"Input file 'test_run_dir/I-DO-NOT-EXIST' not found") + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala new file mode 100644 index 00000000..c4566dd5 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala @@ -0,0 +1,37 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.NoneCompiler +import firrtl.annotations.Annotation +import firrtl.stage.phases.AddDefaults +import firrtl.transforms.BlackBoxTargetDirAnno +import firrtl.stage.{CompilerAnnotation, InfoModeAnnotation} +import firrtl.options.{Phase, TargetDirAnnotation} + +class AddDefaultsSpec extends FlatSpec with Matchers { + + class Fixture { val phase: Phase = new AddDefaults } + + behavior of classOf[AddDefaults].toString + + it should "add expected default annotations and nothing else" in new Fixture { + val expected = Seq( + (a: Annotation) => a match { case BlackBoxTargetDirAnno(b) => b == TargetDirAnnotation().directory }, + (a: Annotation) => a match { case CompilerAnnotation(b) => b.getClass == CompilerAnnotation().compiler.getClass }, + (a: Annotation) => a match { case InfoModeAnnotation(b) => b == InfoModeAnnotation().modeName } ) + + phase.transform(Seq.empty).zip(expected).map { case (x, f) => f(x) should be (true) } + } + + it should "not overwrite existing annotations" in new Fixture { + val input = Seq( + BlackBoxTargetDirAnno("foo"), + CompilerAnnotation(new NoneCompiler()), + InfoModeAnnotation("ignore")) + + phase.transform(input).toSeq should be (input) + } +} diff --git a/src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala new file mode 100644 index 00000000..117e9c5f --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala @@ -0,0 +1,45 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.{EmitAllModulesAnnotation, EmitCircuitAnnotation, HighFirrtlEmitter, VerilogCompiler} +import firrtl.annotations.NoTargetAnnotation +import firrtl.options.Phase +import firrtl.stage.{CompilerAnnotation, RunFirrtlTransformAnnotation} +import firrtl.stage.phases.AddImplicitEmitter + +class AddImplicitEmitterSpec extends FlatSpec with Matchers { + + case class FooAnnotation(x: Int) extends NoTargetAnnotation + case class BarAnnotation(x: String) extends NoTargetAnnotation + + class Fixture { val phase: Phase = new AddImplicitEmitter } + + val someAnnos = Seq(FooAnnotation(1), FooAnnotation(2), BarAnnotation("bar")) + + behavior of classOf[AddImplicitEmitter].toString + + it should "do nothing if no CompilerAnnotation is present" in new Fixture { + phase.transform(someAnnos).toSeq should be (someAnnos) + } + + it should "add an EmitCircuitAnnotation derived from a CompilerAnnotation" in new Fixture { + val input = CompilerAnnotation(new VerilogCompiler) +: someAnnos + val expected = input.flatMap{ + case a@ CompilerAnnotation(b) => Seq(a, + RunFirrtlTransformAnnotation(b.emitter), + EmitCircuitAnnotation(b.emitter.getClass)) + case a => Some(a) + } + phase.transform(input).toSeq should be (expected) + } + + it should "not add an EmitCircuitAnnotation if an EmitAnnotation already exists" in new Fixture { + val input = Seq(CompilerAnnotation(new VerilogCompiler), + EmitAllModulesAnnotation(classOf[HighFirrtlEmitter])) ++ someAnnos + phase.transform(input).toSeq should be (input) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala new file mode 100644 index 00000000..72d186b5 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala @@ -0,0 +1,46 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.{ChirrtlEmitter, EmitAllModulesAnnotation, Parser} +import firrtl.options.Phase +import firrtl.stage.{FirrtlCircuitAnnotation, OutputFileAnnotation} +import firrtl.stage.phases.AddImplicitOutputFile + +class AddImplicitOutputFileSpec extends FlatSpec with Matchers { + + class Fixture { val phase: Phase = new AddImplicitOutputFile } + + val foo = """|circuit Foo: + | module Foo: + | node a = UInt<1>("h0") + |""".stripMargin + + val circuit = Parser.parse(foo) + + behavior of classOf[AddImplicitOutputFile].toString + + it should "default to an output file named 'a'" in new Fixture { + phase.transform(Seq.empty).toSeq should be (Seq(OutputFileAnnotation("a"))) + } + + it should "set the output file based on a FirrtlCircuitAnnotation's main" in new Fixture { + val in = Seq(FirrtlCircuitAnnotation(circuit)) + val out = OutputFileAnnotation(circuit.main) +: in + phase.transform(in).toSeq should be (out) + } + + it should "do nothing if an OutputFileAnnotation or EmitAllModulesAnnotation already exists" in new Fixture { + + info("OutputFileAnnotation works") + val outputFile = Seq(OutputFileAnnotation("Bar"), FirrtlCircuitAnnotation(circuit)) + phase.transform(outputFile).toSeq should be (outputFile) + + info("EmitAllModulesAnnotation works") + val eam = Seq(EmitAllModulesAnnotation(classOf[ChirrtlEmitter]), FirrtlCircuitAnnotation(circuit)) + phase.transform(eam).toSeq should be (eam) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala b/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala new file mode 100644 index 00000000..47aa9b5b --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala @@ -0,0 +1,89 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.stage._ + +import firrtl.{AnnotationSeq, ChirrtlEmitter, EmitAllModulesAnnotation, NoneCompiler} +import firrtl.options.{OptionsException, OutputAnnotationFileAnnotation, Phase} +import firrtl.stage.phases.{AddImplicitOutputFile, Checks} + +class ChecksSpec extends FlatSpec with Matchers { + + class Fixture { val phase: Phase = new Checks } + + val inputFile = FirrtlFileAnnotation("foo") + val outputFile = OutputFileAnnotation("bar") + val emitAllModules = EmitAllModulesAnnotation(classOf[ChirrtlEmitter]) + val outputAnnotationFile = OutputAnnotationFileAnnotation("baz") + val goodCompiler = CompilerAnnotation(new NoneCompiler()) + val infoMode = InfoModeAnnotation("ignore") + + val min = Seq(inputFile, goodCompiler, infoMode) + + def checkExceptionMessage(phase: Phase, annotations: AnnotationSeq, messageStart: String): Unit = + intercept[OptionsException]{ phase.transform(annotations) }.getMessage should startWith(messageStart) + + behavior of classOf[Checks].toString + + it should "require exactly one input source" in new Fixture { + info("0 input source causes an exception") + checkExceptionMessage(phase, Seq.empty, "Unable to determine FIRRTL source to read") + + info("2 input sources causes an exception") + val in = min :+ FirrtlSourceAnnotation("circuit Foo:") + checkExceptionMessage(phase, in, "Multiply defined input FIRRTL sources") + } + + it should "require mutual exclusivity of OutputFileAnnotation and EmitAllModulesAnnotation" in new Fixture { + info("OutputFileAnnotation alone works") + phase.transform(min :+ outputFile) + + info("OneFilePerModuleAnnotation alone works") + phase.transform(min :+ emitAllModules) + + info("Together, they throw an exception") + val in = min ++ Seq(outputFile, emitAllModules) + checkExceptionMessage(phase, in, "Output file is incompatible with emit all modules annotation") + } + + it should "enforce zero or one output files" in new Fixture { + val in = min ++ Seq(outputFile, outputFile) + checkExceptionMessage(phase, in, "No more than one output file can be specified") + } + + it should "enforce exactly one compiler" in new Fixture { + info("0 compilers should throw an exception") + val inZero = Seq(inputFile, infoMode) + checkExceptionMessage(phase, inZero, "Exactly one compiler must be specified, but none found") + + info("2 compilers should throw an exception") + val c = goodCompiler.compiler + val inTwo = min :+ goodCompiler + checkExceptionMessage(phase, inTwo, s"Exactly one compiler must be specified, but found '$c, $c'") + } + + it should "validate info mode names" in new Fixture { + info("Good info mode names should work") + Seq("ignore", "use", "gen", "append") + .map(info => phase.transform(Seq(inputFile, goodCompiler, InfoModeAnnotation(info)))) + } + + it should "enforce exactly one info mode" in new Fixture { + info("0 info modes should throw an exception") + checkExceptionMessage(phase, Seq(inputFile, goodCompiler), + "Exactly one info mode must be specified, but none found") + + info("2 info modes should throw an exception") + val i = infoMode.modeName + checkExceptionMessage(phase, min :+ infoMode, s"Exactly one info mode must be specified, but found '$i, $i'") + } + + it should "pass if the minimum annotations are specified" in new Fixture { + info(s"""Minimum required: ${min.map(_.getClass.getSimpleName).mkString(", ")}""") + phase.transform(min) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala b/src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala new file mode 100644 index 00000000..7a3ab6b7 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala @@ -0,0 +1,143 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.{Compiler => _, _} +import firrtl.options.{Phase, PhasePrerequisiteException} +import firrtl.stage.{CompilerAnnotation, FirrtlCircuitAnnotation, RunFirrtlTransformAnnotation} +import firrtl.stage.phases.Compiler + +class CompilerSpec extends FlatSpec with Matchers { + + class Fixture { val phase: Phase = new Compiler } + + behavior of classOf[Compiler].toString + + it should "do nothing for an empty AnnotationSeq" in new Fixture { + phase.transform(Seq.empty).toSeq should be (empty) + } + + /** A circuit with a parameterized main (top name) that is different at High, Mid, and Low FIRRTL forms. */ + private def chirrtl(main: String): String = + s"""|circuit $main: + | module $main: + | output foo: {bar: UInt} + | + | foo.bar <= UInt<4>("h0") + |""".stripMargin + + it should "compile a circuit to Low FIRRTL when using the Verilog compiler" in new Fixture { + val compiler = new VerilogCompiler + + val circuitIn = Parser.parse(chirrtl("top")) + val circuitOut = compiler.compile(CircuitState(circuitIn, ChirrtlForm), Seq.empty).circuit + + val input = Seq( + FirrtlCircuitAnnotation(circuitIn), + CompilerAnnotation(compiler) ) + + val expected = Seq(FirrtlCircuitAnnotation(circuitOut)) + + phase.transform(input).toSeq should be (expected) + } + + it should "compile multiple FirrtlCircuitAnnotations" in new Fixture { + val (nc, hfc, mfc, lfc, vc, svc) = ( + new NoneCompiler, + new HighFirrtlCompiler, + new MiddleFirrtlCompiler, + new LowFirrtlCompiler, + new VerilogCompiler, + new SystemVerilogCompiler ) + val (ce, hfe, mfe, lfe, ve, sve) = ( + new ChirrtlEmitter, + new HighFirrtlEmitter, + new MiddleFirrtlEmitter, + new LowFirrtlEmitter, + new VerilogEmitter, + new SystemVerilogEmitter ) + + val a = Seq( + /* Default Compiler is HighFirrtlCompiler */ + CompilerAnnotation(hfc), + + /* First compiler group, use NoneCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("a"))), + CompilerAnnotation(nc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + + /* Second compiler group, use default HighFirrtlCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("b"))), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + + /* Third compiler group, use MiddleFirrtlCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("c"))), + CompilerAnnotation(mfc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + RunFirrtlTransformAnnotation(mfe), + EmitCircuitAnnotation(mfe.getClass), + + /* Fourth compiler group, use LowFirrtlCompiler*/ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("d"))), + CompilerAnnotation(lfc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + RunFirrtlTransformAnnotation(mfe), + EmitCircuitAnnotation(mfe.getClass), + RunFirrtlTransformAnnotation(lfe), + EmitCircuitAnnotation(lfe.getClass), + + /* Fifth compiler group, use VerilogCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("e"))), + CompilerAnnotation(vc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + RunFirrtlTransformAnnotation(mfe), + EmitCircuitAnnotation(mfe.getClass), + RunFirrtlTransformAnnotation(lfe), + EmitCircuitAnnotation(lfe.getClass), + RunFirrtlTransformAnnotation(ve), + EmitCircuitAnnotation(ve.getClass), + + /* Sixth compiler group, use SystemVerilogCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("f"))), + CompilerAnnotation(svc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + RunFirrtlTransformAnnotation(mfe), + EmitCircuitAnnotation(mfe.getClass), + RunFirrtlTransformAnnotation(lfe), + EmitCircuitAnnotation(lfe.getClass), + RunFirrtlTransformAnnotation(sve), + EmitCircuitAnnotation(sve.getClass) + ) + + val output = phase.transform(a) + + info("with the same number of output FirrtlCircuitAnnotations") + output + .collect{ case a: FirrtlCircuitAnnotation => a } + .size should be (6) + + info("and all expected EmittedAnnotations should be generated") + output + .collect{ case a: EmittedAnnotation[_] => a } + .size should be (20) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala b/src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala new file mode 100644 index 00000000..4f7ad92a --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala @@ -0,0 +1,79 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import java.io.File + +import firrtl._ + +import firrtl.options.{Phase, TargetDirAnnotation} +import firrtl.stage.OutputFileAnnotation +import firrtl.stage.phases.WriteEmitted + +class WriteEmittedSpec extends FlatSpec with Matchers { + + def removeEmitted(a: AnnotationSeq): AnnotationSeq = a.flatMap { + case a: EmittedAnnotation[_] => None + case a => Some(a) + } + + class Fixture { val phase: Phase = new WriteEmitted } + + behavior of classOf[WriteEmitted].toString + + it should "write emitted circuits" in new Fixture { + val annotations = Seq( + TargetDirAnnotation("test_run_dir/WriteEmittedSpec"), + EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit("foo", "", ".foocircuit")), + EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit("bar", "", ".barcircuit")), + EmittedVerilogCircuitAnnotation(EmittedVerilogCircuit("baz", "", ".bazcircuit")) ) + val expected = Seq("foo.foocircuit", "bar.barcircuit", "baz.bazcircuit") + .map(a => new File(s"test_run_dir/WriteEmittedSpec/$a")) + + info("annotations are unmodified") + phase.transform(annotations).toSeq should be (removeEmitted(annotations).toSeq) + + expected.foreach{ a => + info(s"$a was written") + a should (exist) + a.delete() + } + } + + it should "default to the output file name if one exists" in new Fixture { + val annotations = Seq( + TargetDirAnnotation("test_run_dir/WriteEmittedSpec"), + OutputFileAnnotation("quux"), + EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit("qux", "", ".quxcircuit")) ) + val expected = new File("test_run_dir/WriteEmittedSpec/quux.quxcircuit") + + info("annotations are unmodified") + phase.transform(annotations).toSeq should be (removeEmitted(annotations).toSeq) + + info(s"$expected was written") + expected should (exist) + expected.delete() + } + + it should "write emitted modules" in new Fixture { + val annotations = Seq( + TargetDirAnnotation("test_run_dir/WriteEmittedSpec"), + EmittedFirrtlModuleAnnotation(EmittedFirrtlModule("foo", "", ".foomodule")), + EmittedFirrtlModuleAnnotation(EmittedFirrtlModule("bar", "", ".barmodule")), + EmittedVerilogModuleAnnotation(EmittedVerilogModule("baz", "", ".bazmodule")) ) + val expected = Seq("foo.foomodule", "bar.barmodule", "baz.bazmodule") + .map(a => new File(s"test_run_dir/WriteEmittedSpec/$a")) + + info("EmittedComponent annotations are deleted") + phase.transform(annotations).toSeq should be (removeEmitted(annotations).toSeq) + + expected.foreach{ a => + info(s"$a was written") + a should (exist) + a.delete() + } + } + +} diff --git a/src/test/scala/firrtlTests/transforms/GroupComponentsSpec.scala b/src/test/scala/firrtlTests/transforms/GroupComponentsSpec.scala index c54e02e3..b4c27875 100644 --- a/src/test/scala/firrtlTests/transforms/GroupComponentsSpec.scala +++ b/src/test/scala/firrtlTests/transforms/GroupComponentsSpec.scala @@ -8,7 +8,7 @@ import firrtl.ir._ import FirrtlCheckers._ -class GroupComponentsSpec extends LowTransformSpec { +class GroupComponentsSpec extends MiddleTransformSpec { def transform = new GroupComponents() val top = "Top" def topComp(name: String): ComponentName = ComponentName(name, ModuleName(top, CircuitName(top))) @@ -71,15 +71,18 @@ class GroupComponentsSpec extends LowTransformSpec { | output out: UInt<16> | inst inst of Child | node n = UInt<16>("h0") - | inst.in_IN <= in - | node a = UInt<16>("h0") - | node b = a + | wire a : UInt<16> + | wire b : UInt<16> | out <= inst.w_OUT + | inst.in_IN <= in + | a <= UInt<16>("h0") + | b <= a | module Child : - | input in_IN : UInt<16> | output w_OUT : UInt<16> - | node w = in_IN + | input in_IN : UInt<16> + | wire w : UInt<16> | w_OUT <= w + | w <= in_IN """.stripMargin execute(input, check, groups) } diff --git a/src/test/scala/firrtlTests/transforms/TopWiringTest.scala b/src/test/scala/firrtlTests/transforms/TopWiringTest.scala index 5a6b3420..d5b1aa6d 100644 --- a/src/test/scala/firrtlTests/transforms/TopWiringTest.scala +++ b/src/test/scala/firrtlTests/transforms/TopWiringTest.scala @@ -55,7 +55,7 @@ trait TopWiringTestsCommon extends FirrtlRunners { /** * Tests TopWiring transformation */ -class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { +class TopWiringTests extends MiddleTransformSpec with TopWiringTestsCommon { "The signal x in module C" should s"be connected to Top port with topwiring prefix and outputfile in $testDirName" in { val input = @@ -78,8 +78,8 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -113,7 +113,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C inst c1 and c2" should + "The signal x in module C inst c1 and c2" should s"be connected to Top port with topwiring prefix and outfile in $testDirName" in { val input = """circuit Top : @@ -177,7 +177,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C" should + "The signal x in module C" should s"be connected to Top port with topwiring prefix and outputfile in $testDirName, after name colission" in { val input = """circuit Top : @@ -203,8 +203,8 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -213,14 +213,16 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output topwiring_a1_b1_c1_x_0: UInt<1> | inst a1 of A | inst a2 of A_ - | node topwiring_a1_b1_c1_x = UInt<1>("h0") + | wire topwiring_a1_b1_c1_x : UInt<1> + | topwiring_a1_b1_c1_x <= UInt<1>("h0") | topwiring_a1_b1_c1_x_0 <= a1.topwiring_b1_c1_x_0 | module A : | output x: UInt<1> | output topwiring_b1_c1_x_0: UInt<1> | inst b1 of B - | node topwiring_b1_c1_x = UInt<1>("h0") + | wire topwiring_b1_c1_x : UInt<1> | x <= UInt(1) + | topwiring_b1_c1_x <= UInt<1>("h0") | topwiring_b1_c1_x_0 <= b1.topwiring_c1_x | module A_ : | output x: UInt<1> @@ -240,7 +242,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C" should + "The signal x in module C" should "be connected to Top port with topwiring prefix and no output function" in { val input = """circuit Top : @@ -262,8 +264,8 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_")) val check = """circuit Top : @@ -296,7 +298,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C inst c1 and c2 and signal y in module A_" should + "The signal x in module C inst c1 and c2 and signal y in module A_" should s"be connected to Top port with topwiring prefix and outfile in $testDirName" in { val input = """circuit Top : @@ -321,11 +323,11 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), - TopWiringAnnotation(ComponentName(s"y", - ModuleName(s"A_", CircuitName(s"Top"))), + TopWiringAnnotation(ComponentName(s"y", + ModuleName(s"A_", CircuitName(s"Top"))), s"topwiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -350,8 +352,9 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | module A_ : | output x: UInt<1> | output topwiring_y: UInt<1> - | node y = UInt<1>("h1") + | wire y : UInt<1> | x <= UInt(1) + | y <= UInt<1>("h1") | topwiring_y <= y | module B : | output x: UInt<1> @@ -371,7 +374,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C inst c1 and c2 and signal y in module A_" should + "The signal x in module C inst c1 and c2 and signal y in module A_" should s"be connected to Top port with topwiring and top2wiring prefix and outfile in $testDirName" in { val input = """circuit Top : @@ -396,11 +399,11 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), - TopWiringAnnotation(ComponentName(s"y", - ModuleName(s"A_", CircuitName(s"Top"))), + TopWiringAnnotation(ComponentName(s"y", + ModuleName(s"A_", CircuitName(s"Top"))), s"top2wiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -425,8 +428,9 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | module A_ : | output x: UInt<1> | output top2wiring_y: UInt<1> - | node y = UInt<1>("h1") + | wire y : UInt<1> | x <= UInt(1) + | y <= UInt<1>("h1") | top2wiring_y <= y | module B : | output x: UInt<1> @@ -446,7 +450,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal fullword in module C inst c1 and c2 and signal y in module A_" should + "The signal fullword in module C inst c1 and c2 and signal y in module A_" should s"be connected to Top port with topwiring and top2wiring prefix and outfile in $testDirName" in { val input = """circuit Top : @@ -471,11 +475,11 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output fullword: UInt<1> | fullword <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"fullword", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"fullword", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), - TopWiringAnnotation(ComponentName(s"y", - ModuleName(s"A_", CircuitName(s"Top"))), + TopWiringAnnotation(ComponentName(s"y", + ModuleName(s"A_", CircuitName(s"Top"))), s"top2wiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -500,8 +504,9 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | module A_ : | output fullword: UInt<1> | output top2wiring_y: UInt<1> - | node y = UInt<1>("h1") + | wire y : UInt<1> | fullword <= UInt(1) + | y <= UInt<1>("h1") | top2wiring_y <= y | module B : | output fullword: UInt<1> @@ -576,8 +581,9 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | topwiring_b1_c2_fullword <= b1.topwiring_c2_fullword | module A_ : | output fullword: UInt<1> - | node y = UInt<1>("h1") + | wire y : UInt<1> | fullword <= UInt(1) + | y <= UInt<1>("h1") | module B : | output fullword: UInt<1> | output topwiring_fullword: UInt<1> |
