diff options
Diffstat (limited to 'src/main/scala/firrtl/ExecutionOptionsManager.scala')
| -rw-r--r-- | src/main/scala/firrtl/ExecutionOptionsManager.scala | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/src/main/scala/firrtl/ExecutionOptionsManager.scala b/src/main/scala/firrtl/ExecutionOptionsManager.scala new file mode 100644 index 00000000..05a9f487 --- /dev/null +++ b/src/main/scala/firrtl/ExecutionOptionsManager.scala @@ -0,0 +1,314 @@ +// See License + +package firrtl + +import firrtl.Annotations._ +import firrtl.Parser._ +import firrtl.passes.memlib.ReplSeqMemAnnotation +import scopt.OptionParser + +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 + */ +trait ComposableOptions + +/** + * 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. + */ +case class CommonOptions(topName: String = "", targetDirName: String = "test_run_dir") extends ComposableOptions + +abstract class HasParser(applicationName: String) { + final val parser: OptionParser[Unit] = new OptionParser[Unit](applicationName) {} +} + +trait HasCommonOptions { + self: ExecutionOptionsManager => + var commonOptions = CommonOptions() + + parser.note("common options") + + parser.opt[String]("top-name") + .abbr("tn") + .valueName("<top-level-circuit-name>") + .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("<target-directory>") + .foreach { x => + commonOptions = commonOptions.copy(targetDirName = x) + } + .text(s"This options defines a work directory for intermediate files, default is ${commonOptions.targetDirName}") + + parser.help("help").text("prints this usage text") +} + +/** + * 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 + */ + +case class FirrtlExecutionOptions( + inputFileNameOverride: String = "", + outputFileNameOverride: String = "", + compilerName: String = "verilog", + infoModeName: String = "append", + inferRW: Seq[String] = Seq.empty, + firrtlSource: Option[String] = None, + annotations: List[Annotation] = List.empty) + extends ComposableOptions { + + + 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 "high" => new HighFirrtlCompiler() + case "low" => new LowFirrtlCompiler() + case "verilog" => new VerilogCompiler() + } + } + + def outputSuffix: String = { + compilerName match { + case "verilog" => "v" + case "low" => "lo.fir" + case "high" => "hi.fir" + case _ => + throw new Exception(s"Illegal compiler name $compilerName") + } + } + + /** + * build the input file name, taking overriding parameters + * + * @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 = { + optionsManager.getBuildFileName("fir", inputFileNameOverride) + } + /** + * build the output file name, taking overriding parameters + * + * @param optionsManager this is needed to access build function and its common options + * @return + */ + def getOutputFileName(optionsManager: ExecutionOptionsManager): String = { + optionsManager.getBuildFileName(outputSuffix, outputFileNameOverride) + } +} + +trait HasFirrtlOptions { + self: ExecutionOptionsManager => + var firrtlOptions = FirrtlExecutionOptions() + + parser.note("firrtl options") + + parser.opt[String]("input-file") + .abbr("i") + .valueName ("<firrtl-source>") + .foreach { x => + firrtlOptions = firrtlOptions.copy(inputFileNameOverride = x) + }.text { + "use this to override the top name default, default is empty" + } + + parser.opt[String]("output-file") + .abbr("o") + .valueName ("<output>"). + foreach { x => + firrtlOptions = firrtlOptions.copy(outputFileNameOverride = x) + }.text { + "use this to override the default name, default is empty" + } + + parser.opt[String]("compiler") + .abbr("X") + .valueName ("<high|low|verilog>") + .foreach { x => + firrtlOptions = firrtlOptions.copy(compilerName = x) + } + .validate { x => + if (Array("high", "low", "verilog").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 ("<ignore|use|gen|append>") + .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.infoMode}" + } + + parser.opt[Seq[String]]("inline") + .abbr("fil") + .valueName ("<circuit>[.<module>[.<instance>]][,..],") + .foreach { x => + val newAnnotations = x.map { value => + value.split('.') match { + case Array(circuit) => + passes.InlineAnnotation(CircuitName(circuit), TransID(0)) + case Array(circuit, module) => + passes.InlineAnnotation(ModuleName(module, CircuitName(circuit)), TransID(0)) + case Array(circuit, module, inst) => + passes.InlineAnnotation(ComponentName(inst, ModuleName(module, CircuitName(circuit))), TransID(0)) + } + } + firrtlOptions = firrtlOptions.copy(annotations = firrtlOptions.annotations ++ newAnnotations) + } + .text { + """Inline one or more module (comma separated, no spaces) module looks like "MyModule" or "MyModule.myinstance""" + } + + parser.opt[String]("infer-rw") + .abbr("firw") + .valueName ("<circuit>") + .foreach { x => + firrtlOptions = firrtlOptions.copy( + annotations = firrtlOptions.annotations :+ passes.InferReadWriteAnnotation(x, TransID(-1)) + ) + }.text { + "Enable readwrite port inference for the target circuit" + } + + parser.opt[String]("repl-seq-mem") + .abbr("frsq") + .valueName ("-c:<circuit>:-i:<filename>:-o:<filename>") + .foreach { x => + firrtlOptions = firrtlOptions.copy( + annotations = firrtlOptions.annotations :+ ReplSeqMemAnnotation(x, TransID(-2)) + ) + } + .text { + "Replace sequential memories with blackboxes + configuration file" + } + + parser.note("") + +} + +sealed trait FirrtlExecutionResult + +/** + * 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", "low", or "verilog" + * @param emitted The text result of the compilation, could be verilog or firrtl text. + */ +case class FirrtlExecutionSuccess(emitType: String, emitted: String) extends FirrtlExecutionResult + +/** + * The firrtl compilation failed. + * + * @param message Some kind of hint as to what went wrong. + */ +case class FirrtlExecutionFailure(message: String) extends FirrtlExecutionResult + +/** + * + * @param applicationName The name shown in the usage + */ +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 + } + + s"$directoryName$baseName$normalizedSuffix" + } +} + |
