// 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" } }