diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main/scala/firrtl/Driver.scala | 323 | ||||
| -rw-r--r-- | src/main/scala/firrtl/ExecutionOptionsManager.scala | 314 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/DriverSpec.scala | 158 | ||||
| -rw-r--r-- | src/test/scala/firrtlTests/ExecutionOptionManagerSpec.scala | 41 |
4 files changed, 676 insertions, 160 deletions
diff --git a/src/main/scala/firrtl/Driver.scala b/src/main/scala/firrtl/Driver.scala index 2a5a379a..6c9e34d5 100644 --- a/src/main/scala/firrtl/Driver.scala +++ b/src/main/scala/firrtl/Driver.scala @@ -1,189 +1,192 @@ -/* -Copyright (c) 2014 - 2016 The Regents of the University of -California (Regents). All Rights Reserved. Redistribution and use in -source and binary forms, with or without modification, are permitted -provided that the following conditions are met: - * Redistributions of source code must retain the above - copyright notice, this list of conditions and the following - two paragraphs of disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - two paragraphs of disclaimer in the documentation and/or other materials - provided with the distribution. - * Neither the name of the Regents nor the names of its contributors - may be used to endorse or promote products derived from this - software without specific prior written permission. -IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, -ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF -REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF -ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION -TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR -MODIFICATIONS. -*/ +// See License package firrtl +import java.io.FileNotFoundException + import scala.io.Source -import scala.collection.mutable import Annotations._ -import Utils._ -import Parser.{InfoMode, IgnoreInfo, UseInfo, GenInfo, AppendInfo} +import Parser.{InfoMode, IgnoreInfo} +import scala.collection._ + +/** + * The driver provides methods to access the firrtl compiler. + * Invoke the compiler with either a FirrtlExecutionOption + * + * @example + * {{{ + * val optionsManager = 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 + */ object Driver { - /** - * Implements the default Firrtl compilers and an inlining pass. - * - * Arguments specify the compiler, input file, output file, and - * optionally the module/instances to inline. - */ - def main(args: Array[String]) = { - val usage = """ -Usage: firrtl -i <input_file> -o <output_file> -X <compiler> [options] - sbt "run-main firrtl.Driver -i <input_file> -o <output_file> -X <compiler> [options]" - -Required Arguments: - -i <filename> Specify the input *.fir file - -o <filename> Specify the output file - -X <compiler> Specify the target compiler - Currently supported: high low verilog - -Optional Arguments: - --info-mode <mode> Specify Info Mode - Supported modes: ignore, use, gen, append - --inferRW <circuit> Enable readwrite port inference for the target circuit - --inline <module>|<instance> Inline a module (e.g. "MyModule") or instance (e.g. "MyModule.myinstance") - - --replSeqMem -c:<circuit>:-i:<filename>:-o:<filename> - *** Replace sequential memories with blackboxes + configuration file - *** Input configuration file optional - *** Note: sub-arguments to --replSeqMem should be delimited by : and not white space! - - [--help|-h] Print usage string -""" - - def handleInlineOption(value: String): Annotation = - 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)) - case _ => throw new Exception(s"Bad inline instance/module name: $value" + usage) - } - - def handleInferRWOption(value: String) = - passes.InferReadWriteAnnotation(value, TransID(-1)) - - def handleReplSeqMem(value: String) = - passes.memlib.ReplSeqMemAnnotation(value, TransID(-2)) - - run(args: Array[String], - Map( "high" -> new HighFirrtlCompiler(), - "low" -> new LowFirrtlCompiler(), - "verilog" -> new VerilogCompiler()), - Map("--inline" -> handleInlineOption _, - "--inferRW" -> handleInferRWOption _, - "--replSeqMem" -> handleReplSeqMem _), - usage - ) - } - - // Compiles circuit. First parses a circuit from an input file, // executes all compiler passes, and writes result to an output // file. def compile( - input: String, - output: String, - compiler: Compiler, + input: String, + output: String, + compiler: Compiler, infoMode: InfoMode = IgnoreInfo, - annotations: AnnotationMap = new AnnotationMap(Seq.empty)) = { - val parsedInput = Parser.parse(Source.fromFile(input).getLines, infoMode) + annotations: AnnotationMap = new AnnotationMap(Seq.empty) + ): String = { + val parsedInput = Parser.parse(Source.fromFile(input).getLines(), infoMode) val outputBuffer = new java.io.CharArrayWriter compiler.compile(parsedInput, annotations, outputBuffer) val outputFile = new java.io.PrintWriter(output) - outputFile.write(outputBuffer.toString) + val outputString = outputBuffer.toString + outputFile.write(outputString) outputFile.close() + outputString } /** - * Runs a Firrtl compiler. - * - * @param args list of commandline arguments - * @param compilers mapping a compiler name to a compiler - * @param customOptions mapping a custom option name to a function that returns an annotation - * @param usage describes the commandline API - */ - def run(args: Array[String], compilers: Map[String,Compiler], customOptions: Map[String, String=>Annotation], usage: String) = { - /** - * Keys commandline values specified by user in OptionMap - */ - sealed trait CompilerOption - case object InputFileName extends CompilerOption - case object OutputFileName extends CompilerOption - case object CompilerName extends CompilerOption - case object AnnotationOption extends CompilerOption - case object InfoModeOption extends CompilerOption - /** - * Maps compiler option to user-specified value - */ - type OptionMap = Map[CompilerOption, String] - - /** - * Populated by custom annotations returned from corresponding function - * held in customOptions - */ - val annotations = mutable.ArrayBuffer[Annotation]() - def nextOption(map: OptionMap, list: List[String]): OptionMap = { - list match { - case Nil => map - case "-i" :: value :: tail => - nextOption(map + (InputFileName -> value), tail) - case "-o" :: value :: tail => - nextOption(map + (OutputFileName -> value), tail) - case "-X" :: value :: tail => - nextOption(map + (CompilerName -> value), tail) - case "--info-mode" :: value :: tail => - nextOption(map + (InfoModeOption -> value), tail) - case flag :: value :: tail if customOptions.contains(flag) => - annotations += customOptions(flag)(value) - nextOption(map, tail) - case ("-h" | "--help") :: tail => println(usage); sys.exit(0) - case option :: tail => - throw new Exception("Unknown option " + option + usage) - } - } + * print the message in red + * + * @param message error message + */ + def dramaticError(message: String): Unit = { + println(Console.RED + "-"*78) + println(s"Error: $message") + println("-"*78 + Console.RESET) + } - val arglist = args.toList - val options = nextOption(Map[CompilerOption, String](), arglist) + /** + * Run the firrtl compiler using the provided option + * + * @param optionsManager the desired flags to the compiler + * @return a FirrtlExectionResult indicating success or failure, provide access to emitted data on success + * for downstream tools as desired + */ + def execute(optionsManager: ExecutionOptionsManager with HasFirrtlOptions): FirrtlExecutionResult = { + val firrtlConfig = optionsManager.firrtlOptions + + val firrtlSource = firrtlConfig.firrtlSource match { + case Some(text) => text.split("\n").toIterator + case None => + if(optionsManager.topName.isEmpty && firrtlConfig.inputFileNameOverride.isEmpty) { + val message = "either top-name or input-file-override must be set" + dramaticError(message) + return FirrtlExecutionFailure(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" + dramaticError(message) + return FirrtlExecutionFailure(message) + } + val inputFileName = firrtlConfig.getInputFileName(optionsManager) + try { + io.Source.fromFile(inputFileName).getLines() + } + catch { + case e: FileNotFoundException => + val message = s"Input file $inputFileName not found" + dramaticError(message) + return FirrtlExecutionFailure(message) + } + } - // Get input circuit/output filenames - val input = options.getOrElse(InputFileName, throw new Exception("No input file provided!" + usage)) - val output = options.getOrElse(OutputFileName, throw new Exception("No output file provided!" + usage)) + val parsedInput = Parser.parse(firrtlSource, firrtlConfig.infoMode) + val outputBuffer = new java.io.CharArrayWriter + firrtlConfig.compiler.compile(parsedInput, new AnnotationMap(Seq.empty), outputBuffer) - val infoMode = options.get(InfoModeOption) match { - case (Some("append") | None) => AppendInfo(input) - case Some("use") => UseInfo - case Some("ignore") => IgnoreInfo - case Some("gen") => GenInfo(input) - case Some(other) => throw new Exception("Unknown info mode option: " + other + usage) - } + val outputFileName = firrtlConfig.getOutputFileName(optionsManager) + val outputFile = new java.io.PrintWriter(outputFileName) + val outputString = outputBuffer.toString + outputFile.write(outputString) + outputFile.close() - // Execute selected compiler - error if not recognized compiler - options.get(CompilerName) match { - case Some(name) => - compilers.get(name) match { - case Some(compiler) => compile(input, output, compiler, infoMode, new AnnotationMap(annotations.toSeq)) - case None => throw new Exception("Unknown compiler option: " + name + usage) + FirrtlExecutionSuccess(firrtlConfig.compilerName, outputBuffer.toString) + } + + /** + * 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 + + optionsManager.parse(args) match { + case true => + execute(optionsManager) match { + case success: FirrtlExecutionSuccess => + success + case failure: FirrtlExecutionFailure => + optionsManager.showUsageAsError() + failure + case result => + throw new Exception(s"Error: Unknown Firrtl Execution result $result") } - case None => throw new Exception("No specified compiler option." + usage) + case _ => + FirrtlExecutionFailure("Could not parser command line options") } } + + def main(args: Array[String]): Unit = { + execute(args) + } } + +object FileUtils { + /** + * recursive create directory and all parents + * + * @param directoryName a directory string with one or more levels + * @return + */ + def makeDirectory(directoryName: String): Boolean = { + val dirFile = new java.io.File(directoryName) + if(dirFile.exists()) { + if(dirFile.isDirectory) { + true + } + else { + false + } + } + else { + dirFile.mkdirs() + } + } + + /** + * recursively delete all directories in a relative path + * DO NOT DELETE absolute paths + * + * @param directoryPathName a directory hierarchy to delete + */ + def deleteDirectoryHierarchy(directoryPathName: String): Unit = { + if(directoryPathName.isEmpty || directoryPathName.startsWith("/")) { + // don't delete absolute path + } + else { + val directory = new java.io.File(directoryPathName) + if(directory.isDirectory) { + directory.delete() + val directories = directoryPathName.split("/+").reverse.tail + if (directories.nonEmpty) { + deleteDirectoryHierarchy(directories.reverse.mkString("/")) + } + } + } + } +}
\ No newline at end of file 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" + } +} + diff --git a/src/test/scala/firrtlTests/DriverSpec.scala b/src/test/scala/firrtlTests/DriverSpec.scala new file mode 100644 index 00000000..c2066831 --- /dev/null +++ b/src/test/scala/firrtlTests/DriverSpec.scala @@ -0,0 +1,158 @@ +// See LICENSE for license details. + +package firrtlTests + +import java.io.File + +import firrtl.passes.memlib.ReplSeqMemAnnotation +import org.scalatest.{Matchers, FreeSpec} + +import firrtl._ + +class DriverSpec extends FreeSpec with Matchers { + "CommonOptions are some simple options available across the chisel3 ecosystem" - { + "CommonOption provide an scopt implementation of an OptionParser" - { + "Options can be set from an Array[String] as is passed into a main" - { + "With no arguments default values come out" in { + val optionsManager = new ExecutionOptionsManager("test") + optionsManager.parse(Array.empty[String]) should be(true) + + val commonOptions = optionsManager.commonOptions + commonOptions.topName should be("") + commonOptions.targetDirName should be("test_run_dir") + } + "top name and target can be set" in { + val optionsManager = new ExecutionOptionsManager("test") + optionsManager.parse(Array("--top-name", "dog", "--target-dir", "a/b/c")) should be(true) + val commonOptions = optionsManager.commonOptions + + commonOptions.topName should be("dog") + commonOptions.targetDirName should be("a/b/c") + + optionsManager.getBuildFileName(".fir") should be("a/b/c/dog.fir") + optionsManager.getBuildFileName("fir") should be("a/b/c/dog.fir") + } + } + "CommonOptions can create a directory" in { + var dir = new java.io.File("a/b/c") + if(dir.exists()) { + dir.delete() + } + val optionsManager = new ExecutionOptionsManager("test") + optionsManager.parse(Array("--top-name", "dog", "--target-dir", "a/b/c")) should be (true) + val commonOptions = optionsManager.commonOptions + + commonOptions.topName should be ("dog") + commonOptions.targetDirName should be ("a/b/c") + + optionsManager.makeTargetDir() should be (true) + dir = new java.io.File("a/b/c") + dir.exists() should be (true) + FileUtils.deleteDirectoryHierarchy(commonOptions.targetDirName) + } + } + } + "FirrtlOptions holds option information for the firrtl compiler" - { + "It includes a CommonOptions" in { + val optionsManager = new ExecutionOptionsManager("test") + optionsManager.commonOptions.targetDirName should be ("test_run_dir") + } + "It provides input and output file names based on target" in { + val optionsManager = new ExecutionOptionsManager("test") with HasFirrtlOptions + + optionsManager.parse(Array("--top-name", "cat")) should be (true) + + val firrtlOptions = optionsManager.firrtlOptions + val inputFileName = optionsManager.getBuildFileName("fir", firrtlOptions.inputFileNameOverride) + inputFileName should be ("test_run_dir/cat.fir") + val outputFileName = optionsManager.getBuildFileName("v", firrtlOptions.outputFileNameOverride) + outputFileName should be ("test_run_dir/cat.v") + } + "input and output file names can be overridden, overrides do not use targetDir" in { + val optionsManager = new ExecutionOptionsManager("test") with HasFirrtlOptions + + optionsManager.parse( + Array("--top-name", "cat", "-i", "./bob.fir", "-o", "carol.v") + ) should be (true) + + val firrtlOptions = optionsManager.firrtlOptions + val inputFileName = optionsManager.getBuildFileName("fir", firrtlOptions.inputFileNameOverride) + inputFileName should be ("./bob.fir") + val outputFileName = optionsManager.getBuildFileName("v", firrtlOptions.outputFileNameOverride) + outputFileName should be ("carol.v") + } + "various annotations can be created from command line, currently:" - { + "inline annotation" in { + val optionsManager = new ExecutionOptionsManager("test") with HasFirrtlOptions + + optionsManager.parse( + Array("--inline", "module,module.submodule,module.submodule.instance") + ) should be (true) + + val firrtlOptions = optionsManager.firrtlOptions + firrtlOptions.annotations.length should be (3) + firrtlOptions.annotations.foreach { annotation => + annotation shouldBe a [passes.InlineAnnotation] + } + } + "infer-rw annotation" in { + val optionsManager = new ExecutionOptionsManager("test") with HasFirrtlOptions + + optionsManager.parse( + Array("--infer-rw", "circuit") + ) should be (true) + + val firrtlOptions = optionsManager.firrtlOptions + firrtlOptions.annotations.length should be (1) + firrtlOptions.annotations.foreach { annotation => + annotation shouldBe a [passes.InferReadWriteAnnotation] + } + } + "repl-seq-mem annotation" in { + val optionsManager = new ExecutionOptionsManager("test") with HasFirrtlOptions + + optionsManager.parse( + Array("--repl-seq-mem", "-c:circuit1:-i:infile1:-o:outfile1") + ) should be (true) + + val firrtlOptions = optionsManager.firrtlOptions + + firrtlOptions.annotations.length should be (1) + firrtlOptions.annotations.foreach { annotation => + annotation shouldBe a [ReplSeqMemAnnotation] + } + } + } + } + + val input = + """ + |circuit Dummy : + | module Dummy : + | input x : UInt<1> + | output y : UInt<1> + | y <= x + """.stripMargin + + "Driver produces files with different names depending on the compiler" - { + "compiler changes the default name of the output file" in { + + Seq( + "low" -> "test_run_dir/Dummy.lo.fir", + "high" -> "test_run_dir/Dummy.hi.fir", + "verilog" -> "test_run_dir/Dummy.v" + ).foreach { case (compilerName, expectedOutputFileName) => + val manager = new ExecutionOptionsManager("test") with HasFirrtlOptions { + commonOptions = CommonOptions(topName = "Dummy") + firrtlOptions = FirrtlExecutionOptions(firrtlSource = Some(input), compilerName = compilerName) + } + + firrtl.Driver.execute(manager) + + val file = new File(expectedOutputFileName) + file.exists() should be (true) + file.delete() + } + } + } +} diff --git a/src/test/scala/firrtlTests/ExecutionOptionManagerSpec.scala b/src/test/scala/firrtlTests/ExecutionOptionManagerSpec.scala new file mode 100644 index 00000000..0edef8cc --- /dev/null +++ b/src/test/scala/firrtlTests/ExecutionOptionManagerSpec.scala @@ -0,0 +1,41 @@ +// See LICENSE for license details. + +package firrtlTests + +import firrtl._ +import org.scalatest.{Matchers, FreeSpec} + +class ExecutionOptionManagerSpec extends FreeSpec with Matchers { + "ExecutionOptionManager is a container for one more more ComposableOptions Block" - { + "It has a default CommonOptionsBlock" in { + val manager = new ExecutionOptionsManager("test") + manager.commonOptions.targetDirName should be ("test_run_dir") + } + "But can override defaults like this" in { + val manager = new ExecutionOptionsManager("test") { commonOptions = CommonOptions(topName = "dog") } + manager.commonOptions shouldBe a [CommonOptions] + manager.topName should be ("dog") + manager.commonOptions.topName should be ("dog") + } + "The add method should put a new version of a given type the manager" in { + val manager = new ExecutionOptionsManager("test") { commonOptions = CommonOptions(topName = "dog") } + val initialCommon = manager.commonOptions + initialCommon.topName should be ("dog") + + manager.commonOptions = CommonOptions(topName = "cat") + + val afterCommon = manager.commonOptions + afterCommon.topName should be ("cat") + initialCommon.topName should be ("dog") + } + "multiple composable blocks should be separable" in { + val manager = new ExecutionOptionsManager("test") with HasFirrtlOptions { + commonOptions = CommonOptions(topName = "spoon") + firrtlOptions = FirrtlExecutionOptions(inputFileNameOverride = "fork") + } + + manager.firrtlOptions.inputFileNameOverride should be ("fork") + manager.commonOptions.topName should be ("spoon") + } + } +} |
