aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/scala/firrtl/Driver.scala323
-rw-r--r--src/main/scala/firrtl/ExecutionOptionsManager.scala314
-rw-r--r--src/test/scala/firrtlTests/DriverSpec.scala158
-rw-r--r--src/test/scala/firrtlTests/ExecutionOptionManagerSpec.scala41
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")
+ }
+ }
+}