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..35342a5 --- /dev/null +++ b/src/main/scala/firrtl/stage/phases/DriverCompatibility.scala @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +package firrtl.stage + +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 + +package object phases { + + 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) + + } +} 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 => 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")