From 79963c32e303ef6042c061e0bba1aa6edeec65e5 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 16 Dec 2021 11:39:02 -0800 Subject: [PATCH 1/3] Polyfill formerly deprecated now removed APIs --- src/main/scala/firrtl/Driver.scala | 248 ++++++ .../firrtl/ExecutionOptionsManager.scala | 705 ++++++++++++++++++ src/main/scala/firrtl/stage/package.scala | 58 ++ .../stage/phases/DriverCompatibility.scala | 270 +++++++ .../firrtl_interpreter/vcd/VCDSpec.scala | 4 +- 5 files changed, 1283 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/firrtl/Driver.scala create mode 100644 src/main/scala/firrtl/ExecutionOptionsManager.scala create mode 100644 src/main/scala/firrtl/stage/package.scala create mode 100644 src/main/scala/firrtl/stage/phases/DriverCompatibility.scala diff --git a/src/main/scala/firrtl/Driver.scala b/src/main/scala/firrtl/Driver.scala new file mode 100644 index 0000000..03683fd --- /dev/null +++ b/src/main/scala/firrtl/Driver.scala @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl + +import scala.collection._ +import scala.util.{Failure, Try} +import java.io.{File, FileNotFoundException} +import annotations._ +import firrtl.transforms._ +import firrtl.Utils.throwInternalError +import firrtl.stage.{FirrtlExecutionResultView, FirrtlStage} +import firrtl.stage.phases.DriverCompatibility +import firrtl.options.{Dependency, Phase, PhaseManager, StageUtils, Viewer} +import firrtl.options.phases.DeletedWrapper + +/** + * The driver provides methods to access the firrtl compiler. + * Invoke the compiler with either a FirrtlExecutionOption + * + * @example + * {{{ + * val optionsManager = new ExecutionOptionsManager("firrtl") + * optionsManager.register( + * FirrtlExecutionOptionsKey -> + * new FirrtlExecutionOptions(topName = "Dummy", compilerName = "verilog")) + * firrtl.Driver.execute(optionsManager) + * }}} + * or a series of command line arguments + * @example + * {{{ + * firrtl.Driver.execute(Array("--top-name Dummy --compiler verilog".split(" +")) + * }}} + * each approach has its own endearing aspects + * @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", "FIRRTL 1.2") +object Driver { + + /** Print a warning message + * + * @param message error message + */ + @deprecated("Use firrtl.options.StageUtils.dramaticWarning", "FIRRTL 1.2") + def dramaticWarning(message: String): Unit = StageUtils.dramaticWarning(message) + + /** + * print the message in red + * + * @param message error message + */ + @deprecated("Use firrtl.options.StageUtils.dramaticWarning", "FIRRTL 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 + * update the firrtlOptions with new annotations if it does + */ + @deprecated("Use side-effect free getAnnotation instead", "FIRRTL 1.1") + def loadAnnotations(optionsManager: ExecutionOptionsManager with HasFirrtlOptions): Unit = { + val msg = "Driver.loadAnnotations is deprecated, use Driver.getAnnotations instead" + Driver.dramaticWarning(msg) + optionsManager.firrtlOptions = optionsManager.firrtlOptions.copy( + annotations = Driver.getAnnotations(optionsManager).toList + ) + } + + /** Get annotations from specified files and options + * + * @param optionsManager use optionsManager config to load annotation files + * @return Annotations read from files + */ + def getAnnotations( + optionsManager: ExecutionOptionsManager with HasFirrtlOptions + ): Seq[Annotation] = { + val firrtlConfig = optionsManager.firrtlOptions + + //noinspection ScalaDeprecation + val oldAnnoFileName = firrtlConfig.getAnnotationFileName(optionsManager) + val oldAnnoFile = new File(oldAnnoFileName).getCanonicalFile + + val (annoFiles, usingImplicitAnnoFile) = { + val afs = firrtlConfig.annotationFileNames.map { x => + new File(x).getCanonicalFile + } + // Implicit anno file could be included explicitly, only include it and + // warn if it's not also explicit + val use = oldAnnoFile.exists && !afs.contains(oldAnnoFile) + if (use) (oldAnnoFile +: afs, true) else (afs, false) + } + + // Warnings to get people to change to drop old API + if (firrtlConfig.annotationFileNameOverride.nonEmpty) { + val msg = "annotationFileNameOverride has been removed, file will be ignored! " + + "Use annotationFileNames" + dramaticError(msg) + } else if (usingImplicitAnnoFile) { + val msg = "Implicit .anno file from top-name has been removed, file will be ignored!\n" + + (" " * 9) + "Use explicit -faf option or annotationFileNames" + dramaticError(msg) + } + + val loadedAnnos = annoFiles.flatMap { file => + if (!file.exists) { + throw new AnnotationFileNotFoundException(file) + } + JsonProtocol.deserialize(file) + } + + val targetDirAnno = List(BlackBoxTargetDirAnno(optionsManager.targetDirName)) + + // Output Annotations + val outputAnnos = firrtlConfig.getEmitterAnnos(optionsManager) + + val globalAnnos = Seq(TargetDirAnnotation(optionsManager.targetDirName)) ++ + (if (firrtlConfig.dontCheckCombLoops) Seq(DontCheckCombLoopsAnnotation) else Seq()) ++ + (if (firrtlConfig.noDCE) Seq(NoDCEAnnotation) else Seq()) + + targetDirAnno ++ outputAnnos ++ globalAnnos ++ firrtlConfig.annotations ++ loadedAnnos + } + + private sealed trait FileExtension + private case object FirrtlFile extends FileExtension + private case object ProtoBufFile extends FileExtension + + private def getFileExtension(filename: String): FileExtension = + filename.drop(filename.lastIndexOf('.')) match { + case ".pb" => ProtoBufFile + case _ => FirrtlFile // Default to FIRRTL File + } + + // Useful for handling erros in the options + case class OptionsException(message: String) extends Exception(message) + + /** Get the Circuit from the compile options + * + * Handles the myriad of ways it can be specified + */ + def getCircuit(optionsManager: ExecutionOptionsManager with HasFirrtlOptions): Try[ir.Circuit] = { + val firrtlConfig = optionsManager.firrtlOptions + Try { + // Check that only one "override" is used + val circuitSources = Map( + "firrtlSource" -> firrtlConfig.firrtlSource.isDefined, + "firrtlCircuit" -> firrtlConfig.firrtlCircuit.isDefined, + "inputFileNameOverride" -> firrtlConfig.inputFileNameOverride.nonEmpty + ) + if (circuitSources.values.count(x => x) > 1) { + val msg = circuitSources.collect { case (s, true) => s }.mkString(" and ") + + " are set, only 1 can be set at a time!" + throw new OptionsException(msg) + } + firrtlConfig.firrtlCircuit.getOrElse { + firrtlConfig.firrtlSource.map(x => Parser.parseString(x, firrtlConfig.infoMode)).getOrElse { + if (optionsManager.topName.isEmpty && firrtlConfig.inputFileNameOverride.isEmpty) { + val message = "either top-name or input-file-override must be set" + throw new OptionsException(message) + } + if ( + optionsManager.topName.isEmpty && + firrtlConfig.inputFileNameOverride.nonEmpty && + firrtlConfig.outputFileNameOverride.isEmpty + ) { + val message = "inputFileName set but neither top-name or output-file-override is set" + throw new OptionsException(message) + } + val inputFileName = firrtlConfig.getInputFileName(optionsManager) + try { + // TODO What does InfoMode mean to ProtoBuf? + getFileExtension(inputFileName) match { + case ProtoBufFile => proto.FromProto.fromFile(inputFileName) + case FirrtlFile => Parser.parseFile(inputFileName, firrtlConfig.infoMode) + } + } catch { + case _: FileNotFoundException => + val message = s"Input file $inputFileName not found" + throw new OptionsException(message) + } + } + } + } + } + + /** + * Run the firrtl compiler using the provided option + * + * @param optionsManager the desired flags to the compiler + * @return a FirrtlExecutionResult indicating success or failure, provide access to emitted data on success + * for downstream tools as desired + */ + def execute(optionsManager: ExecutionOptionsManager with HasFirrtlOptions): FirrtlExecutionResult = { + StageUtils.dramaticWarning("firrtl.Driver is deprecated since 1.2!\nPlease switch to firrtl.stage.FirrtlMain") + + val annos = optionsManager.firrtlOptions.toAnnotations ++ optionsManager.commonOptions.toAnnotations + + val phases: Seq[Phase] = { + import DriverCompatibility._ + new PhaseManager( + List( + Dependency[AddImplicitFirrtlFile], + Dependency[AddImplicitAnnotationFile], + Dependency[AddImplicitOutputFile], + Dependency[AddImplicitEmitter], + Dependency[FirrtlStage] + ) + ).transformOrder + .map(DeletedWrapper(_)) + } + + 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) + } + + /** + * this is a wrapper for execute that builds the options from a standard command line args, + * for example, like strings passed to main() + * + * @param args an Array of string s containing legal arguments + * @return + */ + def execute(args: Array[String]): FirrtlExecutionResult = { + val optionsManager = new ExecutionOptionsManager("firrtl") with HasFirrtlOptions + + if (optionsManager.parse(args)) { + execute(optionsManager) match { + case success: FirrtlExecutionSuccess => + success + case failure: FirrtlExecutionFailure => + optionsManager.showUsageAsError() + failure + case result => + throwInternalError(s"Error: Unknown Firrtl Execution result $result") + } + } else { + FirrtlExecutionFailure("Could not parser command line options") + } + } + + def main(args: Array[String]): Unit = { + execute(args) + } +} diff --git a/src/main/scala/firrtl/ExecutionOptionsManager.scala b/src/main/scala/firrtl/ExecutionOptionsManager.scala new file mode 100644 index 0000000..b8c0ded --- /dev/null +++ b/src/main/scala/firrtl/ExecutionOptionsManager.scala @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl + +import logger.LogLevel +import logger.{ClassLogLevelAnnotation, LogClassNamesAnnotation, LogFileAnnotation, LogLevelAnnotation} +import firrtl.annotations._ +import firrtl.Parser.{AppendInfo, GenInfo, IgnoreInfo, InfoMode, UseInfo} +import firrtl.ir.Circuit +import firrtl.passes.memlib.{InferReadWriteAnnotation, ReplSeqMemAnnotation} +import firrtl.passes.clocklist.ClockListAnnotation +import firrtl.transforms.NoCircuitDedupAnnotation +import scopt.OptionParser +import firrtl.stage.{ + CompilerAnnotation, + FirrtlCircuitAnnotation, + FirrtlFileAnnotation, + FirrtlSourceAnnotation, + InfoModeAnnotation, + OutputFileAnnotation, + RunFirrtlTransformAnnotation +} +import firrtl.stage.phases.DriverCompatibility.{EmitOneFilePerModuleAnnotation, TopNameAnnotation} +import firrtl.options.{InputAnnotationFileAnnotation, OutputAnnotationFileAnnotation, ProgramArgsAnnotation, StageUtils} +import firrtl.transforms.{DontCheckCombLoopsAnnotation, NoDCEAnnotation} + +import scala.collection.Seq + +/** + * Use this trait to define an options class that can add its private command line options to a externally + * declared parser. + * '''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", "FIRRTL 1.2") +trait ComposableOptions + +@deprecated("Use firrtl.options.{ExecutionOptionsManager, TerminateOnExit, DuplicateHandling}", "FIRRTL 1.2") +abstract class HasParser(applicationName: String) { + final val parser = new OptionParser[Unit](applicationName) { + var terminateOnExit = true + override def terminate(exitState: Either[String, Unit]): Unit = { + if (terminateOnExit) sys.exit(0) + } + } + + /** + * By default scopt calls sys.exit when --help is in options, this defeats that + */ + def doNotExitOnHelp(): Unit = { + parser.terminateOnExit = false + } + + /** + * By default scopt calls sys.exit when --help is in options, this un-defeats doNotExitOnHelp + */ + def exitOnHelp(): Unit = { + parser.terminateOnExit = true + } +} + +/** + * Most of the chisel toolchain components require a topName which defines a circuit or a device under test. + * Much of the work that is done takes place in a directory. + * It would be simplest to require topName to be defined but in practice it is preferred to defer this. + * 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", "FIRRTL 1.2") +case class CommonOptions( + topName: String = "", + targetDirName: String = ".", + globalLogLevel: LogLevel.Value = LogLevel.None, + logToFile: Boolean = false, + logClassNames: Boolean = false, + classLogLevels: Map[String, LogLevel.Value] = Map.empty, + programArgs: Seq[String] = Seq.empty) + extends ComposableOptions { + + def getLogFileName(optionsManager: ExecutionOptionsManager): String = { + if (topName.isEmpty) { + optionsManager.getBuildFileName("log", "firrtl") + } else { + optionsManager.getBuildFileName("log") + } + } + + def toAnnotations: AnnotationSeq = List() ++ (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", "FIRRTL 1.2") +trait HasCommonOptions { + self: ExecutionOptionsManager => + var commonOptions = CommonOptions() + + parser.note("common options") + + parser + .opt[String]("top-name") + .abbr("tn") + .valueName("") + .foreach { x => + commonOptions = commonOptions.copy(topName = x) + } + .text("This options defines the top level circuit, defaults to dut when possible") + + parser + .opt[String]("target-dir") + .abbr("td") + .valueName("") + .foreach { x => + commonOptions = commonOptions.copy(targetDirName = x) + } + .text(s"This options defines a work directory for intermediate files, default is ${commonOptions.targetDirName}") + + parser + .opt[String]("log-level") + .abbr("ll") + .valueName("") + .foreach { x => + val level = x.toLowerCase match { + case "error" => LogLevel.Error + case "warn" => LogLevel.Warn + case "info" => LogLevel.Info + case "debug" => LogLevel.Debug + case "trace" => LogLevel.Trace + } + commonOptions = commonOptions.copy(globalLogLevel = level) + } + .validate { x => + if (Array("error", "warn", "info", "debug", "trace").contains(x.toLowerCase)) parser.success + else parser.failure(s"$x bad value must be one of error|warn|info|debug|trace") + } + .text(s"This options defines global log level, default is ${commonOptions.globalLogLevel}") + + parser + .opt[Seq[String]]("class-log-level") + .abbr("cll") + .valueName("[,...]") + .foreach { x => + val logAssignments = x.map { y => + val className :: levelName :: _ = y.split(":").toList + + val level = levelName.toLowerCase match { + case "error" => LogLevel.Error + case "warn" => LogLevel.Warn + case "info" => LogLevel.Info + case "debug" => LogLevel.Debug + case "trace" => LogLevel.Trace + case _ => + throw new Exception(s"Error: bad command line arguments for --class-log-level $x") + } + className -> level + } + + commonOptions = commonOptions.copy(classLogLevels = commonOptions.classLogLevels ++ logAssignments) + + } + .text(s"This options defines class log level, default is ${commonOptions.classLogLevels}") + + parser + .opt[Unit]("log-to-file") + .abbr("ltf") + .foreach { _ => + commonOptions = commonOptions.copy(logToFile = true) + } + .text(s"default logs to stdout, this flags writes to topName.log or firrtl.log if no topName") + + parser + .opt[Unit]("log-class-names") + .abbr("lcn") + .foreach { _ => + commonOptions = commonOptions.copy(logClassNames = true) + } + .text(s"shows class names and log level in logging output, useful for target --class-log-level") + + parser.help("help").text("prints this usage text") + + parser + .arg[String]("...") + .unbounded() + .optional() + .action((x, c) => commonOptions = commonOptions.copy(programArgs = commonOptions.programArgs :+ x)) + .text("optional unbounded args") + +} + +/** Firrtl output configuration specified by [[FirrtlExecutionOptions]] + * + * Derived from the fields of the execution options + * @see [[FirrtlExecutionOptions.getOutputConfig]] + */ +sealed abstract class OutputConfig +final case class SingleFile(targetFile: String) extends OutputConfig +final case class OneFilePerModule(targetDir: String) extends OutputConfig + +/** + * The options that firrtl supports in callable component sense + * + * @param inputFileNameOverride default is targetDir/topName.fir + * @param outputFileNameOverride default is targetDir/topName.v the .v is based on the compilerName parameter + * @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", "FIRRTL 1.2") +case class FirrtlExecutionOptions( + inputFileNameOverride: String = "", + outputFileNameOverride: String = "", + compilerName: String = "verilog", + infoModeName: String = "append", + inferRW: Seq[String] = Seq.empty, + firrtlSource: Option[String] = None, + customTransforms: Seq[Transform] = List.empty, + annotations: List[Annotation] = List.empty, + annotationFileNameOverride: String = "", + outputAnnotationFileName: String = "", + emitOneFilePerModule: Boolean = false, + dontCheckCombLoops: Boolean = false, + noDCE: Boolean = false, + annotationFileNames: List[String] = List.empty, + firrtlCircuit: Option[Circuit] = None) + extends ComposableOptions { + + require( + !(emitOneFilePerModule && outputFileNameOverride.nonEmpty), + "Cannot both specify the output filename and emit one file per module!!!" + ) + + def infoMode: InfoMode = { + infoModeName match { + case "use" => UseInfo + case "ignore" => IgnoreInfo + case "gen" => GenInfo(inputFileNameOverride) + case "append" => AppendInfo(inputFileNameOverride) + case other => UseInfo + } + } + + def compiler: Compiler = { + 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() + } + } + + def outputSuffix: String = { + compilerName match { + case "verilog" | "mverilog" => "v" + case "sverilog" => "sv" + case "low" => "lo.fir" + case "middle" => "mid.fir" + case "high" => "hi.fir" + case "none" => "fir" + case _ => + throw new Exception(s"Illegal compiler name $compilerName") + } + } + + /** Get the name of the input file + * + * @note Does not implicitly add a file extension to the input file + * @param optionsManager this is needed to access build function and its common options + * @return a properly constructed input file name + */ + def getInputFileName(optionsManager: ExecutionOptionsManager): String = { + if (inputFileNameOverride.nonEmpty) inputFileNameOverride + else optionsManager.getBuildFileName("fir", inputFileNameOverride) + } + + /** Get the user-specified [[OutputConfig]] + * + * @param optionsManager this is needed to access build function and its common options + * @return the output configuration + */ + def getOutputConfig(optionsManager: ExecutionOptionsManager): OutputConfig = { + if (emitOneFilePerModule) OneFilePerModule(optionsManager.targetDirName) + else SingleFile(optionsManager.getBuildFileName(outputSuffix, outputFileNameOverride)) + } + + /** Get the user-specified targetFile assuming [[OutputConfig]] is [[SingleFile]] + * + * @param optionsManager this is needed to access build function and its common options + * @return the targetFile as a String + */ + def getTargetFile(optionsManager: ExecutionOptionsManager): String = { + getOutputConfig(optionsManager) match { + case SingleFile(targetFile) => targetFile + case other => throw new Exception("OutputConfig is not SingleFile!") + } + } + + /** Gives annotations based on the output configuration + * + * @param optionsManager this is needed to access build function and its common options + * @return Annotations that will be consumed by emitter Transforms + */ + def getEmitterAnnos(optionsManager: ExecutionOptionsManager): Seq[Annotation] = { + // TODO should this be a public function? + val emitter = compilerName match { + case "none" => classOf[ChirrtlEmitter] + case "high" => classOf[HighFirrtlEmitter] + case "middle" => classOf[MiddleFirrtlEmitter] + case "low" => classOf[LowFirrtlEmitter] + case "verilog" => classOf[VerilogEmitter] + case "mverilog" => classOf[MinimumVerilogEmitter] + case "sverilog" => classOf[VerilogEmitter] + } + getOutputConfig(optionsManager) match { + case SingleFile(_) => Seq(EmitCircuitAnnotation(emitter)) + case OneFilePerModule(_) => Seq(EmitAllModulesAnnotation(emitter)) + } + } + + /** + * build the annotation file name, taking overriding parameters + * + * @param optionsManager this is needed to access build function and its common options + * @return + */ + @deprecated("Use FirrtlOptions.annotationFileNames instead", "FIRRTL 1.1") + 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!") + } + + List() ++ (if (inputFileNameOverride.nonEmpty) Seq(FirrtlFileAnnotation(inputFileNameOverride)) else Seq()) ++ + (if (outputFileNameOverride.nonEmpty) { Some(OutputFileAnnotation(outputFileNameOverride)) } + else { None }) ++ + Some(RunFirrtlTransformAnnotation.stringToEmitter(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", "FIRRTL 1.2") +trait HasFirrtlOptions { + self: ExecutionOptionsManager => + var firrtlOptions = FirrtlExecutionOptions() + + parser.note("firrtl options") + + parser + .opt[String]("input-file") + .abbr("i") + .valueName("") + .foreach { x => + firrtlOptions = firrtlOptions.copy(inputFileNameOverride = x) + } + .text { + "use this to override the default input file name , default is empty" + } + + parser + .opt[String]("output-file") + .abbr("o") + .valueName("") + .validate { x => + if (firrtlOptions.emitOneFilePerModule) + parser.failure("Cannot override output-file if split-modules is specified") + else parser.success + } + .foreach { x => + firrtlOptions = firrtlOptions.copy(outputFileNameOverride = x) + } + .text { + "use this to override the default output file name, default is empty" + } + + parser + .opt[String]("annotation-file") + .abbr("faf") + .unbounded() + .valueName("") + .foreach { x => + val annoFiles = x +: firrtlOptions.annotationFileNames + firrtlOptions = firrtlOptions.copy(annotationFileNames = annoFiles) + } + .text("Used to specify annotation files (can appear multiple times)") + + parser + .opt[Unit]("force-append-anno-file") + .abbr("ffaaf") + .hidden() + .foreach { _ => + val msg = "force-append-anno-file is deprecated and will soon be removed\n" + + (" " * 9) + "(It does not do anything anymore)" + StageUtils.dramaticWarning(msg) + } + + parser + .opt[String]("output-annotation-file") + .abbr("foaf") + .valueName("") + .foreach { x => + firrtlOptions = firrtlOptions.copy(outputAnnotationFileName = x) + } + .text { + "use this to set the annotation output file" + } + + parser + .opt[String]("compiler") + .abbr("X") + .valueName("") + .foreach { x => + firrtlOptions = firrtlOptions.copy(compilerName = x) + } + .validate { x => + if (Array("high", "middle", "low", "verilog", "mverilog", "sverilog", "none").contains(x.toLowerCase)) { + parser.success + } else { + parser.failure(s"$x not a legal compiler") + } + } + .text { + s"compiler to use, default is ${firrtlOptions.compilerName}" + } + + parser + .opt[String]("info-mode") + .valueName("") + .foreach { x => + firrtlOptions = firrtlOptions.copy(infoModeName = x.toLowerCase) + } + .validate { x => + if (Array("ignore", "use", "gen", "append").contains(x.toLowerCase)) parser.success + else parser.failure(s"$x bad value must be one of ignore|use|gen|append") + } + .text { + s"specifies the source info handling, default is ${firrtlOptions.infoModeName}" + } + + parser + .opt[Seq[String]]("custom-transforms") + .abbr("fct") + .valueName(".") + .foreach { customTransforms: Seq[String] => + firrtlOptions = firrtlOptions.copy( + customTransforms = firrtlOptions.customTransforms ++ + (customTransforms.map { x: String => + Class.forName(x).asInstanceOf[Class[_ <: Transform]].newInstance() + }) + ) + } + .text { + """runs these custom transforms during compilation.""" + } + + parser + .opt[Seq[String]]("inline") + .abbr("fil") + .valueName("[.[.]][,..],") + .foreach { x => + val newAnnotations = x.map { value => + value.split('.') match { + case Array(circuit) => + passes.InlineAnnotation(CircuitName(circuit)) + case Array(circuit, module) => + passes.InlineAnnotation(ModuleName(module, CircuitName(circuit))) + case Array(circuit, module, inst) => + passes.InlineAnnotation(ComponentName(inst, ModuleName(module, CircuitName(circuit)))) + } + } + firrtlOptions = firrtlOptions.copy( + annotations = firrtlOptions.annotations ++ newAnnotations, + customTransforms = firrtlOptions.customTransforms :+ new passes.InlineInstances + ) + } + .text { + """Inline one or more module (comma separated, no spaces) module looks like "MyModule" or "MyModule.myinstance""" + } + + parser + .opt[Unit]("infer-rw") + .abbr("firw") + .foreach { x => + firrtlOptions = firrtlOptions.copy( + annotations = firrtlOptions.annotations :+ InferReadWriteAnnotation, + customTransforms = firrtlOptions.customTransforms :+ new passes.memlib.InferReadWrite + ) + } + .text { + "Enable readwrite port inference for the target circuit" + } + + parser + .opt[String]("repl-seq-mem") + .abbr("frsq") + .valueName("-c::-i::-o:") + .foreach { x => + firrtlOptions = firrtlOptions.copy( + annotations = firrtlOptions.annotations :+ ReplSeqMemAnnotation.parse(x), + customTransforms = firrtlOptions.customTransforms :+ new passes.memlib.ReplSeqMem + ) + } + .text { + "Replace sequential memories with blackboxes + configuration file" + } + + parser + .opt[String]("list-clocks") + .abbr("clks") + .valueName("-c::-m::-o:") + .foreach { x => + firrtlOptions = firrtlOptions.copy( + annotations = firrtlOptions.annotations :+ ClockListAnnotation.parse(x), + customTransforms = firrtlOptions.customTransforms :+ new passes.clocklist.ClockListTransform + ) + } + .text { + "List which signal drives each clock of every descendent of specified module" + } + + parser + .opt[Unit]("split-modules") + .abbr("fsm") + .validate { x => + if (firrtlOptions.outputFileNameOverride.nonEmpty) + parser.failure("Cannot split-modules if output-file is specified") + else parser.success + } + .foreach { _ => + firrtlOptions = firrtlOptions.copy(emitOneFilePerModule = true) + } + .text { + "Emit each module to its own file in the target directory." + } + + parser + .opt[Unit]("no-check-comb-loops") + .foreach { _ => + firrtlOptions = firrtlOptions.copy(dontCheckCombLoops = true) + } + .text { + "Do NOT check for combinational loops (not recommended)" + } + + parser + .opt[Unit]("no-dce") + .foreach { _ => + firrtlOptions = firrtlOptions.copy(noDCE = true) + } + .text { + "Do NOT run dead code elimination" + } + + parser + .opt[Unit]("no-dedup") + .foreach { _ => + firrtlOptions = firrtlOptions.copy( + annotations = firrtlOptions.annotations :+ NoCircuitDedupAnnotation + ) + } + .text { + "Do NOT dedup modules" + } + + parser.note("") +} + +@deprecated("Use FirrtlStage and examine the output AnnotationSeq directly", "FIRRTL 1.2") +sealed trait FirrtlExecutionResult + +@deprecated("Use FirrtlStage and examine the output AnnotationSeq directly", "FIRRTL 1.2") +object FirrtlExecutionSuccess { + def apply( + emitType: String, + emitted: String, + circuitState: CircuitState + ): FirrtlExecutionSuccess = new FirrtlExecutionSuccess(emitType, emitted, circuitState) + + def unapply(arg: FirrtlExecutionSuccess): Option[(String, String)] = { + Some((arg.emitType, arg.emitted)) + } +} + +/** + * Indicates a successful execution of the firrtl compiler, returning the compiled result and + * the type of compile + * + * @param emitType The name of the compiler used, currently "high", "middle", "low", "verilog", "mverilog", or + * "sverilog" + * @param emitted The emitted result of compilation + */ +@deprecated("Use FirrtlStage and examine the output AnnotationSeq directly", "FIRRTL 1.2") +class FirrtlExecutionSuccess( + val emitType: String, + val emitted: String, + val circuitState: CircuitState) + extends FirrtlExecutionResult + +/** + * The firrtl compilation failed. + * + * @param message Some kind of hint as to what went wrong. + */ +@deprecated("Use FirrtlStage and examine the output AnnotationSeq directly", "FIRRTL 1.2") +case class FirrtlExecutionFailure(message: String) extends FirrtlExecutionResult + +/** + * @param applicationName The name shown in the usage + */ +@deprecated("Use new FirrtlStage infrastructure", "FIRRTL 1.2") +class ExecutionOptionsManager(val applicationName: String) extends HasParser(applicationName) with HasCommonOptions { + + def parse(args: Array[String]): Boolean = { + parser.parse(args) + } + + def showUsageAsError(): Unit = parser.showUsageAsError() + + /** + * make sure that all levels of targetDirName exist + * + * @return true if directory exists + */ + def makeTargetDir(): Boolean = { + FileUtils.makeDirectory(commonOptions.targetDirName) + } + + def targetDirName: String = commonOptions.targetDirName + + /** + * this function sets the topName in the commonOptions. + * It would be nicer to not need this but many chisel tools cannot determine + * the name of the device under test until other options have been parsed. + * Havin this function allows the code to set the TopName after it has been + * determined + * + * @param newTopName the topName to be used + */ + def setTopName(newTopName: String): Unit = { + commonOptions = commonOptions.copy(topName = newTopName) + } + def setTopNameIfNotSet(newTopName: String): Unit = { + if (commonOptions.topName.isEmpty) { + setTopName(newTopName) + } + } + def topName: String = commonOptions.topName + def setTargetDirName(newTargetDirName: String): Unit = { + commonOptions = commonOptions.copy(targetDirName = newTargetDirName) + } + + /** + * return a file based on targetDir, topName and suffix + * Will not add the suffix if the topName already ends with that suffix + * + * @param suffix suffix to add, removes . if present + * @param fileNameOverride this will override the topName if nonEmpty, when using this targetDir is ignored + * @return + */ + def getBuildFileName(suffix: String, fileNameOverride: String = ""): String = { + makeTargetDir() + + val baseName = if (fileNameOverride.nonEmpty) fileNameOverride else topName + val directoryName = { + if (fileNameOverride.nonEmpty) { + "" + } else if (baseName.startsWith("./") || baseName.startsWith("/")) { + "" + } else { + if (targetDirName.endsWith("/")) targetDirName else targetDirName + "/" + } + } + val normalizedSuffix = { + val dottedSuffix = if (suffix.startsWith(".")) suffix else s".$suffix" + if (baseName.endsWith(dottedSuffix)) "" else dottedSuffix + } + val path = directoryName + baseName.split("/").dropRight(1).mkString("/") + FileUtils.makeDirectory(path) + s"$directoryName$baseName$normalizedSuffix" + } +} diff --git a/src/main/scala/firrtl/stage/package.scala b/src/main/scala/firrtl/stage/package.scala new file mode 100644 index 0000000..01a350a --- /dev/null +++ b/src/main/scala/firrtl/stage/package.scala @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl + +import firrtl.annotations.DeletedAnnotation +import firrtl.options.OptionsView +import logger.LazyLogging + +/** 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 { + private[firrtl] implicit object FirrtlExecutionResultView + extends OptionsView[FirrtlExecutionResult] + with LazyLogging { + + def view(options: AnnotationSeq): FirrtlExecutionResult = { + val emittedRes = options.collect { case a: EmittedAnnotation[_] => a.value.value } + .mkString("\n") + + val emitters = options.collect { case RunFirrtlTransformAnnotation(e: Emitter) => e } + if (emitters.length > 1) { + logger.warn( + "More than one emitter used which cannot be accurately represented" + + "in the deprecated FirrtlExecutionResult: " + emitters.map(_.name).mkString(", ") + ) + } + val compilers = options.collect { case CompilerAnnotation(c) => c } + val emitType = emitters.headOption.orElse(compilers.headOption).map(_.name).getOrElse("N/A") + val form = emitters.headOption.orElse(compilers.headOption).map(_.outputForm).getOrElse(UnknownForm) + + options.collectFirst { case a: FirrtlCircuitAnnotation => a.circuit } match { + case None => FirrtlExecutionFailure("No circuit found in AnnotationSeq!") + case Some(a) => + FirrtlExecutionSuccess( + emitType = emitType, + emitted = emittedRes, + circuitState = CircuitState( + circuit = a, + form = form, + annotations = options, + renames = None + ) + ) + } + } + } + +} 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 0000000..7ad7721 --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.stage.phases + +import firrtl.stage._ + +import firrtl.{AnnotationSeq, EmitAllModulesAnnotation, EmitCircuitAnnotation, Emitter, FirrtlExecutionResult, Parser} +import firrtl.annotations.NoTargetAnnotation +import firrtl.FileUtils +import firrtl.proto.FromProto +import firrtl.options.{InputAnnotationFileAnnotation, OptionsException, Phase, StageOptions, StageUtils} +import firrtl.options.Viewer +import firrtl.options.Dependency + +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]]. + */ + 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(FileUtils.getText(f)).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 [[firrtl.options.InputAnnotationFileAnnotation 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 + * [[firrtl.options.InputAnnotationFileAnnotation 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 { + + override def prerequisites = Seq(Dependency[AddImplicitFirrtlFile]) + + override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage]) + + override def invalidates(a: Phase) = false + + /** Try to add an [[firrtl.options.InputAnnotationFileAnnotation 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 { + + override def prerequisites = Seq.empty + + override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage]) + + override def invalidates(a: Phase) = false + + /** 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 [[firrtl.EmitAnnotation 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 { + + override def prerequisites = Seq.empty + + override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage]) + + override def invalidates(a: Phase) = false + + /** 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 @ RunFirrtlTransformAnnotation(e: Emitter) => + if (splitModules) { Seq(a, EmitAllModulesAnnotation(e.getClass)) } + else { Seq(a, EmitCircuitAnnotation(e.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 { + + override def prerequisites = Seq(Dependency[AddImplicitFirrtlFile]) + + override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage]) + + override def invalidates(a: Phase) = false + + /** 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/test/scala/firrtl_interpreter/vcd/VCDSpec.scala b/src/test/scala/firrtl_interpreter/vcd/VCDSpec.scala index 8ae107e..bd98cd4 100644 --- a/src/test/scala/firrtl_interpreter/vcd/VCDSpec.scala +++ b/src/test/scala/firrtl_interpreter/vcd/VCDSpec.scala @@ -26,7 +26,7 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers // scalastyle:off magic.number -class VCDSpec extends AnyFlatSpec with Matchers with BackendCompilationUtilities { +class VCDSpec extends AnyFlatSpec with Matchers { private def getVcd = { VCD("test_circuit") } @@ -132,7 +132,7 @@ class VCDSpec extends AnyFlatSpec with Matchers with BackendCompilationUtilities it should "be able to read a file" in { val tempFile = File.createTempFile("GCD", ".vcd") tempFile.deleteOnExit() - copyResourceToFile("/GCD.vcd", tempFile) + BackendCompilationUtilities.copyResourceToFile("/GCD.vcd", tempFile) val vcdFile = VCD.read(tempFile.getCanonicalPath) vcdFile.date should be ("2016-10-13T16:31+0000") From 22f8be8b985cae32a036f86a0cb9291b1793b55e Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 16 Dec 2021 12:00:43 -0800 Subject: [PATCH 2/3] Deprecate main APIs as firrtl-interpreter is end-of-life --- src/main/scala/firrtl_interpreter/Driver.scala | 4 ++++ src/main/scala/firrtl_interpreter/FirrtlRepl.scala | 2 ++ src/main/scala/firrtl_interpreter/FirrtlTerp.scala | 2 ++ src/main/scala/firrtl_interpreter/InterpretiveTester.scala | 1 + src/main/scala/firrtl_interpreter/ReplConfig.scala | 2 ++ 5 files changed, 11 insertions(+) diff --git a/src/main/scala/firrtl_interpreter/Driver.scala b/src/main/scala/firrtl_interpreter/Driver.scala index 5a78848..7df8f15 100644 --- a/src/main/scala/firrtl_interpreter/Driver.scala +++ b/src/main/scala/firrtl_interpreter/Driver.scala @@ -18,6 +18,7 @@ package firrtl_interpreter import firrtl.{ExecutionOptionsManager, HasFirrtlOptions} +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") case class InterpreterOptions( writeVCD: Boolean = false, vcdShowUnderscored:Boolean = false, @@ -57,6 +58,7 @@ case class InterpreterOptions( } } +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") trait HasInterpreterOptions { self: ExecutionOptionsManager => @@ -188,8 +190,10 @@ object Driver { } } +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") class InterpreterOptionsManager extends ExecutionOptionsManager("interpreter") with HasInterpreterSuite +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") trait HasInterpreterSuite extends ExecutionOptionsManager with HasFirrtlOptions with HasInterpreterOptions { self : ExecutionOptionsManager => } diff --git a/src/main/scala/firrtl_interpreter/FirrtlRepl.scala b/src/main/scala/firrtl_interpreter/FirrtlRepl.scala index 13bde74..36eb15d 100644 --- a/src/main/scala/firrtl_interpreter/FirrtlRepl.scala +++ b/src/main/scala/firrtl_interpreter/FirrtlRepl.scala @@ -39,6 +39,7 @@ abstract class Command(val name: String) { } } +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") class FirrtlRepl(val optionsManager: InterpreterOptionsManager with HasReplConfig) { val replConfig: ReplConfig = optionsManager.replConfig val interpreterOptions: InterpreterOptions = optionsManager.interpreterOptions @@ -1190,6 +1191,7 @@ class FirrtlRepl(val optionsManager: InterpreterOptionsManager with HasReplConfi } } +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") object FirrtlRepl { def execute(optionsManager: InterpreterOptionsManager with HasReplConfig): Unit = { val repl = new FirrtlRepl(optionsManager) diff --git a/src/main/scala/firrtl_interpreter/FirrtlTerp.scala b/src/main/scala/firrtl_interpreter/FirrtlTerp.scala index d217c3f..0f19ce9 100644 --- a/src/main/scala/firrtl_interpreter/FirrtlTerp.scala +++ b/src/main/scala/firrtl_interpreter/FirrtlTerp.scala @@ -36,6 +36,7 @@ import firrtl.ir._ * * @param ast the circuit to be simulated */ +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") class FirrtlTerp(val ast: Circuit, val optionsManager: HasInterpreterSuite) extends SimpleLogger { val interpreterOptions: InterpreterOptions = optionsManager.interpreterOptions @@ -306,6 +307,7 @@ class FirrtlTerp(val ast: Circuit, val optionsManager: HasInterpreterSuite) exte } } +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") object FirrtlTerp { val blackBoxFactory = new DspRealFactory diff --git a/src/main/scala/firrtl_interpreter/InterpretiveTester.scala b/src/main/scala/firrtl_interpreter/InterpretiveTester.scala index 9d19a83..69418ac 100644 --- a/src/main/scala/firrtl_interpreter/InterpretiveTester.scala +++ b/src/main/scala/firrtl_interpreter/InterpretiveTester.scala @@ -31,6 +31,7 @@ import java.io.{File, PrintWriter} * @param input a firrtl program contained in a string * @param optionsManager collection of options for the interpreter */ +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") class InterpretiveTester(input: String, optionsManager: HasInterpreterSuite = new InterpreterOptionsManager) { var expectationsMet = 0 diff --git a/src/main/scala/firrtl_interpreter/ReplConfig.scala b/src/main/scala/firrtl_interpreter/ReplConfig.scala index c8a277f..6c4b931 100644 --- a/src/main/scala/firrtl_interpreter/ReplConfig.scala +++ b/src/main/scala/firrtl_interpreter/ReplConfig.scala @@ -18,6 +18,7 @@ package firrtl_interpreter import firrtl.ExecutionOptionsManager +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") case class ReplConfig( firrtlSourceName: String = "", scriptName: String = "", @@ -27,6 +28,7 @@ case class ReplConfig( runScriptAtStart: Boolean = false) extends firrtl.ComposableOptions +@deprecated("firrtl-interpreter is end-of-life. Use treadle instead.", "firrtl-interpreter 1.5.0") trait HasReplConfig { self: ExecutionOptionsManager => From a1a02609450752e7da59f6b226d4bede30b52071 Mon Sep 17 00:00:00 2001 From: Jack Koenig Date: Thu, 16 Dec 2021 14:08:11 -0800 Subject: [PATCH 3/3] DriverCompatibility => DriverCompatibilityExtensions (#223) DriverCompatibility still exists in firrtl so we want to ensure there are no classpath collisions. Instead, we'll represent polyfill methods via extension methods. This does require an additional import (it's not source compatible), but it's the best we can do. --- .../stage/phases/DriverCompatibility.scala | 256 +----------------- 1 file changed, 9 insertions(+), 247 deletions(-) diff --git a/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala b/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala index 7ad7721..35342a5 100644 --- a/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala +++ b/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -package firrtl.stage.phases +package firrtl.stage import firrtl.stage._ @@ -16,255 +16,17 @@ 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 { +package object phases { - /** 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 + implicit class DriverCompatibilityExtensions(x: DriverCompatibility.type) { - /** 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]]. - */ - 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(FileUtils.getText(f)).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 [[firrtl.options.InputAnnotationFileAnnotation 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 - * [[firrtl.options.InputAnnotationFileAnnotation 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 { - - override def prerequisites = Seq(Dependency[AddImplicitFirrtlFile]) - - override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage]) - - override def invalidates(a: Phase) = false - - /** Try to add an [[firrtl.options.InputAnnotationFileAnnotation InputAnnotationFileAnnotation]] implicitly specified by - * an [[AnnotationSeq]]. + /** Convert an [[firrtl.AnnotationSeq AnnotationSeq]] to a ''deprecated'' [[firrtl.FirrtlExecutionResult + * FirrtlExecutionResult]]. + * @param annotations a sequence of [[firrtl.annotations.Annotation Annotation]] */ - 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 - } - } + @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) } - - /** 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 { - - override def prerequisites = Seq.empty - - override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage]) - - override def invalidates(a: Phase) = false - - /** 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 [[firrtl.EmitAnnotation 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 { - - override def prerequisites = Seq.empty - - override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage]) - - override def invalidates(a: Phase) = false - - /** 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 @ RunFirrtlTransformAnnotation(e: Emitter) => - if (splitModules) { Seq(a, EmitAllModulesAnnotation(e.getClass)) } - else { Seq(a, EmitCircuitAnnotation(e.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 { - - override def prerequisites = Seq(Dependency[AddImplicitFirrtlFile]) - - override def optionalPrerequisiteOf = Seq(Dependency[FirrtlPhase], Dependency[FirrtlStage]) - - override def invalidates(a: Phase) = false - - /** 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 - } - } - - } - }