From b2dd0eb845081609d0aec4a873587ab3f22fe3f7 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Tue, 20 Nov 2018 21:07:52 -0500 Subject: Add FirrtlStage, make Driver compatibility layer This adds FirrtlStage, a reimplementation of the original FIRRTL Driver as a Stage. This updates the original firrtl.options package to implement FirrtlStage (namely, TransformLike is added) along with FirrtlMain. Finally, the original FIRRTL Driver is converted to a compatibility wrapper around FirrtlStage. For background, Stage and Phase form the basis of the Chisel/FIRRTL Hardware Compiler Framework (HCF). A Phase is a class that performs a mathematical transformation on an AnnotationSeq (in effect, a generalization of a FIRRTL transform). Curtly, a Stage is a Phase that also provides a user interface for generating annotations. By their construction, Phases are designed to be composed sequentially into a transformation pipeline. This modifies the existing options package (which provides Stage/Phase) to build out a type hierarchy around Stage/Phase. This adds TransformLike[A] which implements a mathematical transformation over some type A. Additionally, and as an interface between different TransformLikes, this adds Translator[A, B] which extends TransformLike[A], but does an internal transformation over type B. This is used to interface Phases with the existing FIRRTL compiler. This adds a runTransform method to Phase that, like Transform.runTransform, will automatically detect deleted Annotations and generate DeletedAnnotations. The new FirrtlStage, a reimplementation of FIRRTL's Driver, is added as a Stage composed of the following Phases: 1. AddDefaults - add default annotations 2. AddImplicitEmitter - adds an implicit emitter derived from the compiler 3. Checks - sanity check the AnnotationSeq 4. AddCircuit - convert FIRRTL input files/sources to circuits 5. AddImplicitOutputFile - add a default output file 6. Compiler - run the FIRRTL compiler 7. WriteEmitted - write any emitted modules/circuits to files The Driver is converted to a compatibility layer that replicates old Driver behavior. This is implemented by first using new toAnnotation methods for CommonOptions and FirrtlExecutionOptions that enable AnnotationSeq generation. Second, the generated AnnotationSeq is preprocessed and sent to FirrtlStage. The resulting Phase order is then: 1. AddImplicitAnnotationFile - adds a default annotation file 2. AddImplicitFirrtlFile - adds a default FIRRTL file using top name 3. AddImplicitOutputFile - adds an output file from top name 4. AddImplicitEmitter - adds a default emitter derived from a compiler and any split modules command line option 5. FirrtlStage - the aforementioned new FirrtlStage Finally, the output AnnotationSeq is then viewed as a FirrtlExecutionResult. This compatibility layer enables uninterrupted usage of old Driver infrastructure, e.g., FirrtlExecutionOptions and CommonOptions can still be mutated directly and used to run the Driver. This results in differing behavior between the new FirrtlStage and the old Driver, specifically: - FirrtlStage makes a clear delineation between a "compiler" and an "emitter". These are defined using separate options. A compiler is "-X/--compiler", while an emitter is one of "-E/--emit-circuit" or "-e/--emit-modules". - Related to the above, the "-fsm/--split-modules" has been removed from the FirrtlStage. This option is confusing once an implicit emitter is removed. It is also unclear how this should be handled once the user can specify multiple emitters, e.g., which emitter should "--split-modules" apply to? - WriteOutputAnnotations will, by default, not write DeletedAnnotations to the output file. - The old top name ("-tn/--top-name") option has been removed from FirrtlStage. This option is really a means to communicate what input and output files are as opposed to anything associated with the circuit name. This option is preserved for the Driver compatibility layer. Additionally, this changes existing transform scheduling to work for emitters (which subclass Transform). Previously, one emitter was explicitly scheduled at the end of all transforms for a given compiler. Additional emitters could be added, but they would be scheduled as transforms. This fixes this to rely on transform scheduling for all emitters. In slightly more detail: 1. The explicit emitter is removed from Compiler.compile 2. An explicit emitter is added to Compiler.compileAndEmit 3. Compiler.mergeTransforms will schedule emitters as late as possible, i.e., all emitters will occur after transforms that output their input form. 4. All AddImplicitEmitter phases (DriverCompatibility and normal) will add RunFirrtlTransformAnnotations to add implicit emitters The FIRRTL fat jar utilities are changed to point at FirrtlStage and not at the Driver. This has backwards incompatibility issues for users that are using the utilities directly, e.g., Rocket Chip. The Logger has been updated with methods for setting options based on an AnnotationSeq. This migrates the Logger to use AnnotationSeq as input parameters, e.g., for makeScope. Old-style methods are left in place and deprecated. However, the Logger is not itself a Stage. The options of Logger Annotations are included in the base Shell and Stage is updated to wrap its Phases in a Logger scope. Additionally, this changes any code that does option parsing to always prepend an annotation as opposed to appending an annotation. This is faster, but standardizing on this has implications for dealing with the parallel compilation annotation ordering. A Shell will now put the initial annotations first (in the order the user specified) and then place all annotations generating from parsing after that. This adds a test case to verify this behavior. Discovered custom transforms (via `RunFirrtlTransformAnnotation`s) are discovered by the compiler phase in a user-specified order, but are stored in reverse order to more efficiently prepend (as opposed to append) to a list. This now reverses the transform order before execution to preserve backwards compatibility of custom transform ordering. The Compiler phase also generates one deleted annotation for each `RunFirrtlTransformAnnotation`. These are also reversed. Miscellaneous small changes: - Split main method of Stage into StageMain class - Only mix in HasScoptOptions into Annotation companion objects (h/t @jackkoenig) - Store Compiler in CompilerAnnotation - CompilerNameAnnotation -> CompilerAnnotation - Make Emitter abstract in outputSuffix (move out of FirrtlOptions) - Add DriverCompatibility.AddImplicitOutputFile that will add an output file annotation based on the presence of a TopNameAnnotation. This is important for compatibility with the old Driver. - Cleanup Scaladoc - Refactor CircuitOption to be abstract in "toCircuit" that converts the option to a FirrtlCircuitAnnotation. This allows more of the conversion steps to be moved out of AddCircuit and into the actual annotation. - Add WriteDeletedAnnotation to module WriteOutputAnnotations - A method for accessing a FirrtlExecutionResultView is exposed in FIRRTL's DriverCompatibilityLayer - Using "--top-name/-tn" or "--split-modules/-fsm" with FirrtlStage generates an error indicating that this option is no longer supported - Using FirrtlStage without at least one emitter will generate a warning - Use vals for emitter in Compiler subclasses (these are used to build RunFirrtlTransformAnnotations and the object should be stable for comparisons) - Fixes to tests that use LowTransformSpec instead of MiddleTransformSpec. (SimpleTransformSpec is dumb and won't schedule transforms correctly. If you rely on an emitter, you need to use the right transform spec to test your transform if you're relying on an emitter.) Signed-off-by: Schuyler Eldridge --- src/test/scala/firrtlTests/DriverSpec.scala | 31 +++++++--- .../annotationTests/TargetDirAnnotationSpec.scala | 1 - src/test/scala/firrtlTests/options/ShellSpec.scala | 20 ++++++- .../transforms/GroupComponentsSpec.scala | 15 +++-- .../firrtlTests/transforms/TopWiringTest.scala | 68 ++++++++++++---------- 5 files changed, 89 insertions(+), 46 deletions(-) (limited to 'src/test') diff --git a/src/test/scala/firrtlTests/DriverSpec.scala b/src/test/scala/firrtlTests/DriverSpec.scala index ae1e08e7..d26aadf2 100644 --- a/src/test/scala/firrtlTests/DriverSpec.scala +++ b/src/test/scala/firrtlTests/DriverSpec.scala @@ -206,18 +206,35 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities } // Deprecated - "Annotations can be read implicitly from the name of the circuit" in { + "Annotations can be read implicitly from the name of the circuit" - { + val input = """|circuit foo : + | module foo : + | input x : UInt<8> + | output y : UInt<8> + | y <= x""".stripMargin val top = "foo" val optionsManager = new ExecutionOptionsManager("test") with HasFirrtlOptions { commonOptions = commonOptions.copy(topName = top) + firrtlOptions = firrtlOptions.copy(firrtlSource = Some(input)) } val annoFile = new File(optionsManager.commonOptions.targetDirName, top + ".anno") - copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) - optionsManager.firrtlOptions.annotations.length should be(0) - val annos = Driver.getAnnotations(optionsManager) - annos.length should be(12) // 9 from circuit plus 3 general purpose - annos.count(_.isInstanceOf[InlineAnnotation]) should be(9) - annoFile.delete() + "Using Driver.getAnnotations" in { + copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) + optionsManager.firrtlOptions.annotations.length should be(0) + val annos = Driver.getAnnotations(optionsManager) + annos.length should be(12) // 9 from circuit plus 3 general purpose + annos.count(_.isInstanceOf[InlineAnnotation]) should be(9) + annoFile.delete() + } + "Using Driver.execute" in { + copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) + Driver.execute(optionsManager) match { + case r: FirrtlExecutionSuccess => + val annos = r.circuitState.annotations + annos.count(_.isInstanceOf[InlineAnnotation]) should be(9) + } + annoFile.delete() + } } // Deprecated diff --git a/src/test/scala/firrtlTests/annotationTests/TargetDirAnnotationSpec.scala b/src/test/scala/firrtlTests/annotationTests/TargetDirAnnotationSpec.scala index 8f157131..eb061d8f 100644 --- a/src/test/scala/firrtlTests/annotationTests/TargetDirAnnotationSpec.scala +++ b/src/test/scala/firrtlTests/annotationTests/TargetDirAnnotationSpec.scala @@ -5,7 +5,6 @@ package annotationTests import firrtlTests._ import firrtl._ -import firrtl.stage.TargetDirAnnotation /** Looks for [[TargetDirAnnotation]] */ class FindTargetDirTransform(expected: String) extends Transform { diff --git a/src/test/scala/firrtlTests/options/ShellSpec.scala b/src/test/scala/firrtlTests/options/ShellSpec.scala index d87a9a30..50000550 100644 --- a/src/test/scala/firrtlTests/options/ShellSpec.scala +++ b/src/test/scala/firrtlTests/options/ShellSpec.scala @@ -2,12 +2,24 @@ package firrtlTests.options -import org.scalatest._ +import org.scalatest.{FlatSpec, Matchers} +import firrtl.annotations.NoTargetAnnotation import firrtl.options.Shell class ShellSpec extends FlatSpec with Matchers { + case object A extends NoTargetAnnotation + case object B extends NoTargetAnnotation + case object C extends NoTargetAnnotation + case object D extends NoTargetAnnotation + case object E extends NoTargetAnnotation + + trait AlphabeticalCli { this: Shell => + parser.opt[Unit]('c', "c-option").unbounded().action( (x, c) => C +: c ) + parser.opt[Unit]('d', "d-option").unbounded().action( (x, c) => D +: c ) + parser.opt[Unit]('e', "e-option").unbounded().action( (x, c) => E +: c ) } + behavior of "Shell" it should "detect all registered libraries and transforms" in { @@ -19,4 +31,10 @@ class ShellSpec extends FlatSpec with Matchers { info("Found BarLibrary") shell.registeredLibraries.map(_.getClass.getName) should contain ("firrtlTests.options.BarLibrary") } + + it should "correctly order annotations and options" in { + val shell = new Shell("foo") with AlphabeticalCli + + shell.parse(Array("-c", "-d", "-e"), Seq(A, B)).toSeq should be (Seq(A, B, C, D, E)) + } } diff --git a/src/test/scala/firrtlTests/transforms/GroupComponentsSpec.scala b/src/test/scala/firrtlTests/transforms/GroupComponentsSpec.scala index c54e02e3..b4c27875 100644 --- a/src/test/scala/firrtlTests/transforms/GroupComponentsSpec.scala +++ b/src/test/scala/firrtlTests/transforms/GroupComponentsSpec.scala @@ -8,7 +8,7 @@ import firrtl.ir._ import FirrtlCheckers._ -class GroupComponentsSpec extends LowTransformSpec { +class GroupComponentsSpec extends MiddleTransformSpec { def transform = new GroupComponents() val top = "Top" def topComp(name: String): ComponentName = ComponentName(name, ModuleName(top, CircuitName(top))) @@ -71,15 +71,18 @@ class GroupComponentsSpec extends LowTransformSpec { | output out: UInt<16> | inst inst of Child | node n = UInt<16>("h0") - | inst.in_IN <= in - | node a = UInt<16>("h0") - | node b = a + | wire a : UInt<16> + | wire b : UInt<16> | out <= inst.w_OUT + | inst.in_IN <= in + | a <= UInt<16>("h0") + | b <= a | module Child : - | input in_IN : UInt<16> | output w_OUT : UInt<16> - | node w = in_IN + | input in_IN : UInt<16> + | wire w : UInt<16> | w_OUT <= w + | w <= in_IN """.stripMargin execute(input, check, groups) } diff --git a/src/test/scala/firrtlTests/transforms/TopWiringTest.scala b/src/test/scala/firrtlTests/transforms/TopWiringTest.scala index 5a6b3420..d5b1aa6d 100644 --- a/src/test/scala/firrtlTests/transforms/TopWiringTest.scala +++ b/src/test/scala/firrtlTests/transforms/TopWiringTest.scala @@ -55,7 +55,7 @@ trait TopWiringTestsCommon extends FirrtlRunners { /** * Tests TopWiring transformation */ -class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { +class TopWiringTests extends MiddleTransformSpec with TopWiringTestsCommon { "The signal x in module C" should s"be connected to Top port with topwiring prefix and outputfile in $testDirName" in { val input = @@ -78,8 +78,8 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -113,7 +113,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C inst c1 and c2" should + "The signal x in module C inst c1 and c2" should s"be connected to Top port with topwiring prefix and outfile in $testDirName" in { val input = """circuit Top : @@ -177,7 +177,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C" should + "The signal x in module C" should s"be connected to Top port with topwiring prefix and outputfile in $testDirName, after name colission" in { val input = """circuit Top : @@ -203,8 +203,8 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -213,14 +213,16 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output topwiring_a1_b1_c1_x_0: UInt<1> | inst a1 of A | inst a2 of A_ - | node topwiring_a1_b1_c1_x = UInt<1>("h0") + | wire topwiring_a1_b1_c1_x : UInt<1> + | topwiring_a1_b1_c1_x <= UInt<1>("h0") | topwiring_a1_b1_c1_x_0 <= a1.topwiring_b1_c1_x_0 | module A : | output x: UInt<1> | output topwiring_b1_c1_x_0: UInt<1> | inst b1 of B - | node topwiring_b1_c1_x = UInt<1>("h0") + | wire topwiring_b1_c1_x : UInt<1> | x <= UInt(1) + | topwiring_b1_c1_x <= UInt<1>("h0") | topwiring_b1_c1_x_0 <= b1.topwiring_c1_x | module A_ : | output x: UInt<1> @@ -240,7 +242,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C" should + "The signal x in module C" should "be connected to Top port with topwiring prefix and no output function" in { val input = """circuit Top : @@ -262,8 +264,8 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_")) val check = """circuit Top : @@ -296,7 +298,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C inst c1 and c2 and signal y in module A_" should + "The signal x in module C inst c1 and c2 and signal y in module A_" should s"be connected to Top port with topwiring prefix and outfile in $testDirName" in { val input = """circuit Top : @@ -321,11 +323,11 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), - TopWiringAnnotation(ComponentName(s"y", - ModuleName(s"A_", CircuitName(s"Top"))), + TopWiringAnnotation(ComponentName(s"y", + ModuleName(s"A_", CircuitName(s"Top"))), s"topwiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -350,8 +352,9 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | module A_ : | output x: UInt<1> | output topwiring_y: UInt<1> - | node y = UInt<1>("h1") + | wire y : UInt<1> | x <= UInt(1) + | y <= UInt<1>("h1") | topwiring_y <= y | module B : | output x: UInt<1> @@ -371,7 +374,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal x in module C inst c1 and c2 and signal y in module A_" should + "The signal x in module C inst c1 and c2 and signal y in module A_" should s"be connected to Top port with topwiring and top2wiring prefix and outfile in $testDirName" in { val input = """circuit Top : @@ -396,11 +399,11 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output x: UInt<1> | x <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"x", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), - TopWiringAnnotation(ComponentName(s"y", - ModuleName(s"A_", CircuitName(s"Top"))), + TopWiringAnnotation(ComponentName(s"y", + ModuleName(s"A_", CircuitName(s"Top"))), s"top2wiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -425,8 +428,9 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | module A_ : | output x: UInt<1> | output top2wiring_y: UInt<1> - | node y = UInt<1>("h1") + | wire y : UInt<1> | x <= UInt(1) + | y <= UInt<1>("h1") | top2wiring_y <= y | module B : | output x: UInt<1> @@ -446,7 +450,7 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { execute(input, check, topwiringannos) } - "The signal fullword in module C inst c1 and c2 and signal y in module A_" should + "The signal fullword in module C inst c1 and c2 and signal y in module A_" should s"be connected to Top port with topwiring and top2wiring prefix and outfile in $testDirName" in { val input = """circuit Top : @@ -471,11 +475,11 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | output fullword: UInt<1> | fullword <= UInt(0) """.stripMargin - val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"fullword", - ModuleName(s"C", CircuitName(s"Top"))), + val topwiringannos = Seq(TopWiringAnnotation(ComponentName(s"fullword", + ModuleName(s"C", CircuitName(s"Top"))), s"topwiring_"), - TopWiringAnnotation(ComponentName(s"y", - ModuleName(s"A_", CircuitName(s"Top"))), + TopWiringAnnotation(ComponentName(s"y", + ModuleName(s"A_", CircuitName(s"Top"))), s"top2wiring_"), TopWiringOutputFilesAnnotation(testDirName, topWiringTestOutputFilesFunction)) val check = @@ -500,8 +504,9 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | module A_ : | output fullword: UInt<1> | output top2wiring_y: UInt<1> - | node y = UInt<1>("h1") + | wire y : UInt<1> | fullword <= UInt(1) + | y <= UInt<1>("h1") | top2wiring_y <= y | module B : | output fullword: UInt<1> @@ -576,8 +581,9 @@ class TopWiringTests extends LowTransformSpec with TopWiringTestsCommon { | topwiring_b1_c2_fullword <= b1.topwiring_c2_fullword | module A_ : | output fullword: UInt<1> - | node y = UInt<1>("h1") + | wire y : UInt<1> | fullword <= UInt(1) + | y <= UInt<1>("h1") | module B : | output fullword: UInt<1> | output topwiring_fullword: UInt<1> -- cgit v1.2.3 From 254e7909f6c9d155f514664584f142566f0a6799 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Tue, 4 Dec 2018 01:35:48 -0500 Subject: Add tests for Annotations/Options refactor - Add tests for DriverCompatibility.AddImplicitEmitter - Add tests for DriverCompatibility.AddImplicitOutputFile - Use a different top name in DriverSpec emit circuit tests for better coverage - Add tests for DriverCompatibility.WriteEmitted - Add catchWrites firrtlTests utility to intercept file writes - Add tests for WriteOutputAnnotations - Add tests for --custom-transforms reversing Signed-off-by: Schuyler Eldridge --- .../phases/tests/DriverCompatibilitySpec.scala | 219 +++++++++++ src/test/scala/firrtlTests/DriverSpec.scala | 38 +- src/test/scala/firrtlTests/FirrtlSpec.scala | 84 ++++ .../firrtlTests/options/OptionParserSpec.scala | 65 ++-- .../firrtlTests/options/phases/ChecksSpec.scala | 48 +++ .../options/phases/GetIncludesSpec.scala | 96 +++++ .../phases/WriteOutputAnnotationsSpec.scala | 108 ++++++ .../scala/firrtlTests/stage/FirrtlCliSpec.scala | 34 ++ .../scala/firrtlTests/stage/FirrtlMainSpec.scala | 421 +++++++++++++++++++++ .../firrtlTests/stage/FirrtlOptionsViewSpec.scala | 70 ++++ .../firrtlTests/stage/phases/AddCircuitSpec.scala | 101 +++++ .../firrtlTests/stage/phases/AddDefaultsSpec.scala | 37 ++ .../stage/phases/AddImplicitEmitterSpec.scala | 45 +++ .../stage/phases/AddImplicitOutputFileSpec.scala | 46 +++ .../firrtlTests/stage/phases/ChecksSpec.scala | 89 +++++ .../firrtlTests/stage/phases/CompilerSpec.scala | 143 +++++++ .../stage/phases/WriteEmittedSpec.scala | 79 ++++ 17 files changed, 1673 insertions(+), 50 deletions(-) create mode 100644 src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala create mode 100644 src/test/scala/firrtlTests/options/phases/ChecksSpec.scala create mode 100644 src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala create mode 100644 src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala create mode 100644 src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala (limited to 'src/test') diff --git a/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala b/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala new file mode 100644 index 00000000..77316feb --- /dev/null +++ b/src/test/scala/firrtl/stage/phases/tests/DriverCompatibilitySpec.scala @@ -0,0 +1,219 @@ +// See LICENSE for license details. + +package firrtl.stage.phases.tests + +import org.scalatest.{FlatSpec, Matchers, PrivateMethodTester} + +import scala.io.Source + +import java.io.File + +import firrtl._ +import firrtl.stage._ +import firrtl.stage.phases.DriverCompatibility._ + +import firrtl.options.{InputAnnotationFileAnnotation, Phase, TargetDirAnnotation} +import firrtl.stage.phases.DriverCompatibility + +class DriverCompatibilitySpec extends FlatSpec with Matchers with PrivateMethodTester { + + class PhaseFixture(val phase: Phase) + + /* This method wraps some magic that lets you use the private method DriverCompatibility.topName */ + def topName(annotations: AnnotationSeq): Option[String] = { + val topName = PrivateMethod[Option[String]]('topName) + DriverCompatibility invokePrivate topName(annotations) + } + + def simpleCircuit(main: String): String = s"""|circuit $main: + | module $main: + | node x = UInt<1>("h0") + |""".stripMargin + + /* This is a tuple holding an annotation that can be used to derive a top name and the expected top name for that + * annotation. If these annotations are presented here in the same order that DriverCompatibility.topName uses to + * discern a top name. E.g., a TopNameAnnotation is always used, even in the presence of a FirrtlSourceAnnotation. + * Note: the last two FirrtlFileAnnotations have equal precedence, but the first one in the AnnotationSeq wins. + */ + val annosWithTops = Seq( + (TopNameAnnotation("foo"), "foo"), + (FirrtlCircuitAnnotation(Parser.parse(simpleCircuit("bar"))), "bar"), + (FirrtlSourceAnnotation(simpleCircuit("baz")), "baz"), + (FirrtlFileAnnotation("src/test/resources/integration/PipeTester.fir"), "PipeTester"), + (FirrtlFileAnnotation("src/test/resources/integration/GCDTester.pb"), "GCDTester") + ) + + behavior of s"${DriverCompatibility.getClass.getName}.topName (private method)" + + /* This iterates over the tails of annosWithTops. Using the ordering of annosWithTops, if this AnnotationSeq is fed to + * DriverCompatibility.topName, the head annotation will be used to determine the top name. This test ensures that + * topName behaves as expected. + */ + for ( t <- annosWithTops.tails ) t match { + case Nil => + it should "return None on an empty AnnotationSeq" in { + topName(Seq.empty) should be (None) + } + case x => + val annotations = x.map(_._1) + val top = x.head._2 + it should s"determine a top name ('$top') from a ${annotations.head.getClass.getName}" in { + topName(annotations).get should be (top) + } + } + + def createFile(name: String): Unit = { + val file = new File(name) + file.getParentFile.getCanonicalFile.mkdirs() + file.createNewFile() + } + + behavior of classOf[AddImplicitAnnotationFile].toString + + val testDir = "test_run_dir/DriverCompatibilitySpec" + + it should "not modify the annotations if an InputAnnotationFile already exists" in + new PhaseFixture(new AddImplicitAnnotationFile) { + + createFile(testDir + "/foo.anno") + val annotations = Seq( + InputAnnotationFileAnnotation("bar.anno"), + TargetDirAnnotation(testDir), + TopNameAnnotation("foo") ) + + phase.transform(annotations).toSeq should be (annotations) + } + + it should "add an InputAnnotationFile based on a derived topName" in + new PhaseFixture(new AddImplicitAnnotationFile) { + createFile(testDir + "/bar.anno") + val annotations = Seq( + TargetDirAnnotation(testDir), + TopNameAnnotation("bar") ) + + val expected = annotations.toSet + + InputAnnotationFileAnnotation(testDir + "/bar.anno") + + phase.transform(annotations).toSet should be (expected) + } + + it should "not add an InputAnnotationFile for .anno.json annotations" in + new PhaseFixture(new AddImplicitAnnotationFile) { + createFile(testDir + "/baz.anno.json") + val annotations = Seq( + TargetDirAnnotation(testDir), + TopNameAnnotation("baz") ) + + phase.transform(annotations).toSeq should be (annotations) + } + + it should "not add an InputAnnotationFile if it cannot determine the topName" in + new PhaseFixture(new AddImplicitAnnotationFile) { + val annotations = Seq( TargetDirAnnotation(testDir) ) + + phase.transform(annotations).toSeq should be (annotations) + } + + behavior of classOf[AddImplicitFirrtlFile].toString + + it should "not modify the annotations if a CircuitOption is present" in + new PhaseFixture(new AddImplicitFirrtlFile) { + val annotations = Seq( FirrtlFileAnnotation("foo"), TopNameAnnotation("bar") ) + + phase.transform(annotations).toSeq should be (annotations) + } + + it should "add an FirrtlFileAnnotation if a TopNameAnnotation is present" in + new PhaseFixture(new AddImplicitFirrtlFile) { + val annotations = Seq( TopNameAnnotation("foo") ) + val expected = annotations.toSet + + FirrtlFileAnnotation(new File("foo.fir").getCanonicalPath) + + phase.transform(annotations).toSet should be (expected) + } + + it should "do nothing if no TopNameAnnotation is present" in + new PhaseFixture(new AddImplicitFirrtlFile) { + val annotations = Seq( TargetDirAnnotation("foo") ) + + phase.transform(annotations).toSeq should be (annotations) + } + + behavior of classOf[AddImplicitEmitter].toString + + val (nc, hfc, mfc, lfc, vc, svc) = ( new NoneCompiler, + new HighFirrtlCompiler, + new MiddleFirrtlCompiler, + new LowFirrtlCompiler, + new VerilogCompiler, + new SystemVerilogCompiler ) + + it should "convert CompilerAnnotations into EmitCircuitAnnotations without EmitOneFilePerModuleAnnotation" in + new PhaseFixture(new AddImplicitEmitter) { + val annotations = Seq( + CompilerAnnotation(nc), + CompilerAnnotation(hfc), + CompilerAnnotation(mfc), + CompilerAnnotation(lfc), + CompilerAnnotation(vc), + CompilerAnnotation(svc) + ) + val expected = annotations + .flatMap( a => Seq(a, + RunFirrtlTransformAnnotation(a.compiler.emitter), + EmitCircuitAnnotation(a.compiler.emitter.getClass)) ) + + phase.transform(annotations).toSeq should be (expected) + } + + it should "convert CompilerAnnotations into EmitAllodulesAnnotation with EmitOneFilePerModuleAnnotation" in + new PhaseFixture(new AddImplicitEmitter) { + val annotations = Seq( + EmitOneFilePerModuleAnnotation, + CompilerAnnotation(nc), + CompilerAnnotation(hfc), + CompilerAnnotation(mfc), + CompilerAnnotation(lfc), + CompilerAnnotation(vc), + CompilerAnnotation(svc) + ) + val expected = annotations + .flatMap{ + case a: CompilerAnnotation => Seq(a, + RunFirrtlTransformAnnotation(a.compiler.emitter), + EmitAllModulesAnnotation(a.compiler.emitter.getClass)) + case a => Seq(a) + } + + phase.transform(annotations).toSeq should be (expected) + } + + behavior of classOf[AddImplicitOutputFile].toString + + it should "add an OutputFileAnnotation derived from a TopNameAnnotation if no OutputFileAnnotation exists" in + new PhaseFixture(new AddImplicitOutputFile) { + val annotations = Seq( TopNameAnnotation("foo") ) + val expected = Seq( + OutputFileAnnotation("foo"), + TopNameAnnotation("foo") + ) + phase.transform(annotations).toSeq should be (expected) + } + + it should "do nothing if an OutputFileannotation already exists" in + new PhaseFixture(new AddImplicitOutputFile) { + val annotations = Seq( + TopNameAnnotation("foo"), + OutputFileAnnotation("bar") ) + val expected = annotations + phase.transform(annotations).toSeq should be (expected) + } + + it should "do nothing if no TopNameAnnotation exists" in + new PhaseFixture(new AddImplicitOutputFile) { + val annotations = Seq.empty + val expected = annotations + phase.transform(annotations).toSeq should be (expected) + } + +} diff --git a/src/test/scala/firrtlTests/DriverSpec.scala b/src/test/scala/firrtlTests/DriverSpec.scala index d26aadf2..1cb991c8 100644 --- a/src/test/scala/firrtlTests/DriverSpec.scala +++ b/src/test/scala/firrtlTests/DriverSpec.scala @@ -218,6 +218,7 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities firrtlOptions = firrtlOptions.copy(firrtlSource = Some(input)) } val annoFile = new File(optionsManager.commonOptions.targetDirName, top + ".anno") + val vFile = new File(optionsManager.commonOptions.targetDirName, top + ".v") "Using Driver.getAnnotations" in { copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) optionsManager.firrtlOptions.annotations.length should be(0) @@ -225,6 +226,7 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities annos.length should be(12) // 9 from circuit plus 3 general purpose annos.count(_.isInstanceOf[InlineAnnotation]) should be(9) annoFile.delete() + vFile.delete() } "Using Driver.execute" in { copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) @@ -234,6 +236,7 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities annos.count(_.isInstanceOf[InlineAnnotation]) should be(9) } annoFile.delete() + vFile.delete() } } @@ -384,20 +387,29 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities "To a single file with file extension depending on the compiler by default" in { Seq( - "none" -> "./Top.fir", - "low" -> "./Top.lo.fir", - "high" -> "./Top.hi.fir", - "middle" -> "./Top.mid.fir", - "verilog" -> "./Top.v", - "mverilog" -> "./Top.v", - "sverilog" -> "./Top.sv" + "none" -> "./Foo.fir", + "low" -> "./Foo.lo.fir", + "high" -> "./Foo.hi.fir", + "middle" -> "./Foo.mid.fir", + "verilog" -> "./Foo.v", + "mverilog" -> "./Foo.v", + "sverilog" -> "./Foo.sv" ).foreach { case (compilerName, expectedOutputFileName) => + info(s"$compilerName -> $expectedOutputFileName") val manager = new ExecutionOptionsManager("test") with HasFirrtlOptions { - commonOptions = CommonOptions(topName = "Top") + commonOptions = CommonOptions(topName = "Foo") firrtlOptions = FirrtlExecutionOptions(firrtlSource = Some(input), compilerName = compilerName) } - firrtl.Driver.execute(manager) + firrtl.Driver.execute(manager) match { + case success: FirrtlExecutionSuccess => + success.emitted.size should not be (0) + success.circuitState.annotations.length should be > (0) + case a: FirrtlExecutionFailure => + fail(s"Got a FirrtlExecutionFailure! Expected FirrtlExecutionSuccess. Full message:\n${a.message}") + } + + val file = new File(expectedOutputFileName) file.exists() should be(true) @@ -414,9 +426,8 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities "mverilog" -> Seq("./Top.v", "./Child.v"), "sverilog" -> Seq("./Top.sv", "./Child.sv") ).foreach { case (compilerName, expectedOutputFileNames) => - println(s"$compilerName -> $expectedOutputFileNames") + info(s"$compilerName -> $expectedOutputFileNames") val manager = new ExecutionOptionsManager("test") with HasFirrtlOptions { - commonOptions = CommonOptions(topName = "Top") firrtlOptions = FirrtlExecutionOptions(firrtlSource = Some(input), compilerName = compilerName, emitOneFilePerModule = true) @@ -424,9 +435,10 @@ class DriverSpec extends FreeSpec with Matchers with BackendCompilationUtilities firrtl.Driver.execute(manager) match { case success: FirrtlExecutionSuccess => + success.emitted.size should not be (0) success.circuitState.annotations.length should be > (0) - case _ => - + case failure: FirrtlExecutionFailure => + fail(s"Got a FirrtlExecutionFailure! Expected FirrtlExecutionSuccess. Full message:\n${failure.message}") } for (name <- expectedOutputFileNames) { diff --git a/src/test/scala/firrtlTests/FirrtlSpec.scala b/src/test/scala/firrtlTests/FirrtlSpec.scala index c3c25cd5..c110109c 100644 --- a/src/test/scala/firrtlTests/FirrtlSpec.scala +++ b/src/test/scala/firrtlTests/FirrtlSpec.scala @@ -3,6 +3,7 @@ package firrtlTests import java.io._ +import java.security.Permission import com.typesafe.scalalogging.LazyLogging @@ -320,3 +321,86 @@ abstract class CompilationTest(name: String, dir: String) extends FirrtlPropSpec compileFirrtlTest(name, dir) } } + +trait Utils { + + /** Run some Scala thunk and return STDOUT and STDERR as strings. + * @param thunk some Scala code + * @return a tuple containing STDOUT, STDERR, and what the thunk returns + */ + def grabStdOutErr[T](thunk: => T): (String, String, T) = { + val stdout, stderr = new ByteArrayOutputStream() + val ret = scala.Console.withOut(stdout) { scala.Console.withErr(stderr) { thunk } } + (stdout.toString, stderr.toString, ret) + } + + /** Encodes a System.exit exit code + * @param status the exit code + */ + private case class ExitException(status: Int) extends SecurityException(s"Found a sys.exit with code $status") + + /** A security manager that converts calls to System.exit into [[ExitException]]s by explicitly disabling the ability of + * a thread to actually exit. For more information, see: + * - https://docs.oracle.com/javase/tutorial/essential/environment/security.html + */ + private class ExceptOnExit extends SecurityManager { + override def checkPermission(perm: Permission): Unit = {} + override def checkPermission(perm: Permission, context: Object): Unit = {} + override def checkExit(status: Int): Unit = { + super.checkExit(status) + throw ExitException(status) + } + } + + /** Encodes a file that some code tries to write to + * @param the file name + */ + private case class WriteException(file: String) extends SecurityException(s"Tried to write to file $file") + + /** A security manager that converts writes to any file into [[WriteException]]s. + */ + private class ExceptOnWrite extends SecurityManager { + override def checkPermission(perm: Permission): Unit = {} + override def checkPermission(perm: Permission, context: Object): Unit = {} + override def checkWrite(file: String): Unit = { + super.checkWrite(file) + throw WriteException(file) + } + } + + /** Run some Scala code (a thunk) in an environment where all System.exit are caught and returned. This avoids a + * situation where a test results in something actually exiting and killing the entire test. This is necessary if you + * want to test a command line program, e.g., the `main` method of [[firrtl.options.Stage Stage]]. + * + * NOTE: THIS WILL NOT WORK IN SITUATIONS WHERE THE THUNK IS CATCHING ALL [[Exception]]s OR [[Throwable]]s, E.G., + * SCOPT. IF THIS IS HAPPENING THIS WILL NOT WORK. REPEAT THIS WILL NOT WORK. + * @param thunk some Scala code + * @return either the output of the thunk (`Right[T]`) or an exit code (`Left[Int]`) + */ + def catchStatus[T](thunk: => T): Either[Int, T] = { + try { + System.setSecurityManager(new ExceptOnExit()) + Right(thunk) + } catch { + case ExitException(a) => Left(a) + } finally { + System.setSecurityManager(null) + } + } + + /** Run some Scala code (a thunk) in an environment where file writes are caught and the file that a program tries to + * write to is returned. This is useful if you want to test that some thunk either tries to write to a specific file + * or doesn't try to write at all. + */ + def catchWrites[T](thunk: => T): Either[String, T] = { + try { + System.setSecurityManager(new ExceptOnWrite()) + Right(thunk) + } catch { + case WriteException(a) => Left(a) + } finally { + System.setSecurityManager(null) + } + } + +} diff --git a/src/test/scala/firrtlTests/options/OptionParserSpec.scala b/src/test/scala/firrtlTests/options/OptionParserSpec.scala index ae4899d4..0a9ac2d5 100644 --- a/src/test/scala/firrtlTests/options/OptionParserSpec.scala +++ b/src/test/scala/firrtlTests/options/OptionParserSpec.scala @@ -4,15 +4,13 @@ package firrtlTests.options import firrtl.{AnnotationSeq, FIRRTLException} import firrtl.annotations.{Annotation, NoTargetAnnotation} -import firrtl.options.{DoNotTerminateOnExit, DuplicateHandling, OptionsException} +import firrtl.options.{DoNotTerminateOnExit, DuplicateHandling, ExceptOnError, OptionsException} import scopt.OptionParser import org.scalatest.{FlatSpec, Matchers} -import java.security.Permission - -class OptionParserSpec extends FlatSpec with Matchers { +class OptionParserSpec extends FlatSpec with Matchers with firrtlTests.Utils { case class IntAnnotation(x: Int) extends NoTargetAnnotation { def extract: Int = x @@ -32,50 +30,35 @@ class OptionParserSpec extends FlatSpec with Matchers { opt[Int]("integer").abbr("m").unbounded.action( (x, c) => IntAnnotation(x) +: c ) } - case class ExitException(status: Option[Int]) extends SecurityException("Found a sys.exit") + trait WithIntParser { val parser = new IntParser } - /* Security manager that disallows calls to sys.exit */ - class ExceptOnExit extends SecurityManager { - override def checkPermission(perm: Permission): Unit = {} - override def checkPermission(perm: Permission, context: Object): Unit = {} - override def checkExit(status: Int): Unit = { - super.checkExit(status) - throw ExitException(Some(status)) - } - } + behavior of "A default OptionsParser" - /* Tell a parser to terminate in an environment where sys.exit throws an exception */ - def catchStatus(parser: OptionParser[_], exitState: Either[String, Unit]): Option[Int] = { - System.setSecurityManager(new ExceptOnExit()) - val status = try { - parser.terminate(exitState) - throw new ExitException(None) - } catch { - case ExitException(s) => s - } - System.setSecurityManager(null) - status - } + it should "call sys.exit if terminate is called" in new WithIntParser { + info("exit status of 1 for failure") + catchStatus { parser.terminate(Left("some message")) } should be (Left(1)) - behavior of "default OptionsParser" - - it should "terminate on exit" in { - val parser = new IntParser + info("exit status of 0 for success") + catchStatus { parser.terminate(Right(Unit)) } should be (Left(0)) + } - info("By default, exit statuses are reported") - catchStatus(parser, Left("some message")) should be (Some(1)) - catchStatus(parser, Right(Unit)) should be (Some(0)) + it should "print to stderr on an invalid option" in new WithIntParser { + grabStdOutErr{ parser.parse(Array("--foo"), Seq[Annotation]()) }._2 should include ("Unknown option --foo") } - behavior of "DoNotTerminateOnExit" + behavior of "An OptionParser with DoNotTerminateOnExit mixed in" it should "disable sys.exit for terminate method" in { val parser = new IntParser with DoNotTerminateOnExit - catchStatus(parser, Left("some message")) should be (None) - catchStatus(parser, Right(Unit)) should be (None) + + info("no exit for failure") + catchStatus { parser.terminate(Left("some message")) } should be (Right(())) + + info("no exit for success") + catchStatus { parser.terminate(Right(Unit)) } should be (Right(())) } - behavior of "DuplicateHandling" + behavior of "An OptionParser with DuplicateHandling mixed in" it should "detect short duplicates" in { val parser = new IntParser with DuplicateHandling with DuplicateShortOption @@ -89,4 +72,12 @@ class OptionParserSpec extends FlatSpec with Matchers { .getMessage should startWith ("Duplicate long option") } + behavior of "An OptionParser with ExceptOnError mixed in" + + it should "cause an OptionsException on an invalid option" in { + val parser = new IntParser with ExceptOnError + intercept[OptionsException] { parser.parse(Array("--foo"), Seq[Annotation]()) } + .getMessage should include ("Unknown option") + } + } diff --git a/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala b/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala new file mode 100644 index 00000000..e04ba554 --- /dev/null +++ b/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala @@ -0,0 +1,48 @@ +// See LICENSE for license details. + +package firrtlTests.options.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.AnnotationSeq +import firrtl.options.{OptionsException, OutputAnnotationFileAnnotation, Phase, TargetDirAnnotation} +import firrtl.options.phases.Checks + +class ChecksSpec extends FlatSpec with Matchers { + + val targetDir = TargetDirAnnotation("foo") + val annoOut = OutputAnnotationFileAnnotation("bar") + + /* A minimum annotation Seq that will pass [[Checks]] */ + val min = Seq(targetDir) + + def checkExceptionMessage(phase: Phase, annotations: AnnotationSeq, messageStart: String): Unit = + intercept[OptionsException]{ phase.transform(annotations) }.getMessage should startWith(messageStart) + + class Fixture { val phase: Phase = new Checks } + + behavior of classOf[Checks].toString + + it should "enforce exactly one TargetDirAnnotation" in new Fixture { + info("0 target directories throws an exception") + checkExceptionMessage(phase, Seq.empty, "Exactly one target directory must be specified") + + info("2 target directories throws an exception") + checkExceptionMessage(phase, Seq(targetDir, targetDir), "Exactly one target directory must be specified") + } + + it should "enforce zero or one output annotation files" in new Fixture { + info("0 output annotation files is okay") + phase.transform(Seq(targetDir)) + + info("2 output annotation files throws an exception") + val in = Seq(targetDir, annoOut, annoOut) + checkExceptionMessage(phase, in, "At most one output annotation file can be specified") + } + + it should "pass if the minimum annotations are specified" in new Fixture { + info(s"""Minimum required: ${min.map(_.getClass.getSimpleName).mkString(", ")}""") + phase.transform(min) + } + +} diff --git a/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala b/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala new file mode 100644 index 00000000..5b07d0e0 --- /dev/null +++ b/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala @@ -0,0 +1,96 @@ +// See LICENSE for license details. + +package firrtlTests.options.phases + +import org.scalatest.{FlatSpec, Matchers} + +import java.io.{File, PrintWriter} + +import firrtl.AnnotationSeq +import firrtl.annotations.{Annotation, AnnotationFileNotFoundException, JsonProtocol, + NoTargetAnnotation} +import firrtl.options.phases.GetIncludes +import firrtl.options.{InputAnnotationFileAnnotation, Phase} +import firrtl.util.BackendCompilationUtilities + +case object A extends NoTargetAnnotation +case object B extends NoTargetAnnotation +case object C extends NoTargetAnnotation +case object D extends NoTargetAnnotation +case object E extends NoTargetAnnotation + +class GetIncludesSpec extends FlatSpec with Matchers with BackendCompilationUtilities with firrtlTests.Utils { + + + val dir = new File("test_run_dir/GetIncludesSpec") + dir.mkdirs() + + def ref(filename: String): InputAnnotationFileAnnotation = InputAnnotationFileAnnotation(s"$dir/$filename.anno.json") + + def checkAnnos(a: AnnotationSeq, b: AnnotationSeq): Unit = { + info("read the expected number of annotations") + a.size should be (b.size) + + info("annotations match exact order") + a.zip(b).foreach{ case (ax, bx) => ax should be (bx) } + } + + val files = Seq( + new File(dir + "/a.anno.json") -> Seq(A, ref("b")), + new File(dir + "/b.anno.json") -> Seq(B, ref("c"), ref("a")), + new File(dir + "/c.anno.json") -> Seq(C, ref("d"), ref("e")), + new File(dir + "/d.anno.json") -> Seq(D), + new File(dir + "/e.anno.json") -> Seq(E) + ) + + files.foreach{ case (file, annotations) => + val pw = new PrintWriter(file) + pw.write(JsonProtocol.serialize(annotations)) + pw.close() + } + + class Fixture { val phase: Phase = new GetIncludes } + + behavior of classOf[GetIncludes].toString + + it should "throw an exception if the annotation file doesn't exit" in new Fixture { + intercept[AnnotationFileNotFoundException]{ phase.transform(Seq(ref("f"))) } + .getMessage should startWith("Annotation file") + } + + it should "read annotations from a file" in new Fixture { + val e = ref("e") + val in = Seq(e) + val expect = Seq(E) + val out = phase.transform(in) + + checkAnnos(out, expect) + } + + it should "read annotations from multiple files, but not reading duplicates" in new Fixture { + val Seq(d, e) = Seq("d", "e").map(ref) + val in = Seq(d, e, e, d) + val expect = Seq(D, E) + val (stdout, _, out) = grabStdOutErr { phase.transform(in) } + + checkAnnos(out, expect) + + Seq("d", "e").foreach{ x => + info(s"a warning about '$x.anno.json' was printed") + stdout should include (s"Warning: Annotation file ($dir/$x.anno.json) already included!") + } + } + + it should "handle recursive references gracefully, but show a warning" in new Fixture { + val Seq(a, b, c, d, e) = Seq("a", "b", "c", "d", "e").map(ref) + val in = Seq(a) + val expect = Seq(A, B, C, D, E) + val (stdout, _, out) = grabStdOutErr { phase.transform(in) } + + checkAnnos(out, expect) + + info("a warning about 'a.anno.json' was printed") + stdout should include (s"Warning: Annotation file ($dir/a.anno.json)") + } + +} diff --git a/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala b/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala new file mode 100644 index 00000000..07aad53f --- /dev/null +++ b/src/test/scala/firrtlTests/options/phases/WriteOutputAnnotationsSpec.scala @@ -0,0 +1,108 @@ +// See LICENSE for license details. + +package firrtlTests.options.phases + +import org.scalatest.{FlatSpec, Matchers} + +import java.io.File + +import firrtl.{AnnotationSeq, EmittedFirrtlCircuitAnnotation, EmittedFirrtlCircuit} +import firrtl.annotations.{DeletedAnnotation, NoTargetAnnotation} +import firrtl.options.{InputAnnotationFileAnnotation, OutputAnnotationFileAnnotation, Phase, WriteDeletedAnnotation} +import firrtl.options.phases.{GetIncludes, WriteOutputAnnotations} +import firrtl.stage.FirrtlFileAnnotation + +class WriteOutputAnnotationsSpec extends FlatSpec with Matchers with firrtlTests.Utils { + + val dir = "test_run_dir/WriteOutputAnnotationSpec" + + /** Check if the annotations contained by a [[File]] and the same, and in the same order, as a reference + * [[AnnotationSeq]]. This uses [[GetIncludes]] as that already knows how to read annotation files. + * @param f a file to read + * @param a the expected annotations + */ + private def fileContainsAnnotations(f: File, a: AnnotationSeq): Unit = { + info(s"output file '$f' exists") + f should (exist) + + info(s"reading '$f' works") + val read = (new GetIncludes) + .transform(Seq(InputAnnotationFileAnnotation(f.toString))) + .filterNot{ + case a @ DeletedAnnotation(_, _: InputAnnotationFileAnnotation) => true + case _ => false } + + info(s"annotations in file are expected size") + read.size should be (a.size) + + read + .zip(a) + .foreach{ case (read, expected) => + info(s"$read matches") + read should be (expected) } + + f.delete() + } + + class Fixture { val phase: Phase = new WriteOutputAnnotations } + + behavior of classOf[WriteOutputAnnotations].toString + + it should "write annotations to a file (excluding DeletedAnnotations)" in new Fixture { + val file = new File(dir + "/should-write-annotations-to-a-file.anno.json") + val annotations = Seq( OutputAnnotationFileAnnotation(file.toString), + WriteOutputAnnotationsSpec.FooAnnotation, + WriteOutputAnnotationsSpec.BarAnnotation(0), + WriteOutputAnnotationsSpec.BarAnnotation(1), + DeletedAnnotation("foo", WriteOutputAnnotationsSpec.FooAnnotation) ) + val expected = annotations.filter { + case a: DeletedAnnotation => false + case a => true + } + val out = phase.transform(annotations) + + info("annotations are unmodified") + out.toSeq should be (annotations) + + fileContainsAnnotations(file, expected) + } + + it should "include DeletedAnnotations if a WriteDeletedAnnotation is present" in new Fixture { + val file = new File(dir + "should-include-deleted.anno.json") + val annotations = Seq( OutputAnnotationFileAnnotation(file.toString), + WriteOutputAnnotationsSpec.FooAnnotation, + WriteOutputAnnotationsSpec.BarAnnotation(0), + WriteOutputAnnotationsSpec.BarAnnotation(1), + DeletedAnnotation("foo", WriteOutputAnnotationsSpec.FooAnnotation), + WriteDeletedAnnotation ) + val out = phase.transform(annotations) + + info("annotations are unmodified") + out.toSeq should be (annotations) + + fileContainsAnnotations(file, annotations) + } + + it should "do nothing if no output annotation file is specified" in new Fixture { + val annotations = Seq( WriteOutputAnnotationsSpec.FooAnnotation, + WriteOutputAnnotationsSpec.BarAnnotation(0), + WriteOutputAnnotationsSpec.BarAnnotation(1) ) + + val out = catchWrites { phase.transform(annotations) } match { + case Right(a) => + info("no file writes occurred") + a + case Left(a) => + fail(s"No file writes expected, but a write to '$a' ocurred!") + } + + info("annotations are unmodified") + out.toSeq should be (annotations) + } + +} + +private object WriteOutputAnnotationsSpec { + case object FooAnnotation extends NoTargetAnnotation + case class BarAnnotation(x: Int) extends NoTargetAnnotation +} diff --git a/src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala new file mode 100644 index 00000000..f13a498f --- /dev/null +++ b/src/test/scala/firrtlTests/stage/FirrtlCliSpec.scala @@ -0,0 +1,34 @@ +// See LICENSE for license details. + +package firrtlTests.stage + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.stage.RunFirrtlTransformAnnotation +import firrtl.options.Shell +import firrtl.stage.FirrtlCli + +class FirrtlCliSpec extends FlatSpec with Matchers { + + behavior of "FirrtlCli for RunFirrtlTransformAnnotation / -fct / --custom-transforms" + + it should "preserver transform order" in { + val shell = new Shell("foo") with FirrtlCli + val args = Array( + "--custom-transforms", "firrtl.transforms.BlackBoxSourceHelper,firrtl.transforms.CheckCombLoops", + "--custom-transforms", "firrtl.transforms.CombineCats", + "--custom-transforms", "firrtl.transforms.ConstantPropagation" ) + val expected = Seq( + classOf[firrtl.transforms.BlackBoxSourceHelper], + classOf[firrtl.transforms.CheckCombLoops], + classOf[firrtl.transforms.CombineCats], + classOf[firrtl.transforms.ConstantPropagation] ) + + shell + .parse(args) + .collect{ case a: RunFirrtlTransformAnnotation => a } + .zip(expected) + .map{ case (RunFirrtlTransformAnnotation(a), b) => a.getClass should be (b) } + } + +} diff --git a/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala new file mode 100644 index 00000000..ca766d52 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/FirrtlMainSpec.scala @@ -0,0 +1,421 @@ +// See LICENSE for license details. + +package firrtlTests.stage + +import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers} + +import java.io.{File, PrintWriter} + +import scala.io.Source + +import firrtl.stage.FirrtlMain +import firrtl.util.BackendCompilationUtilities + +/** Testing for the top-level [[FirrtlStage]] via [[FirrtlMain]]. + * + * This test uses the [[org.scalatest.FeatureSpec FeatureSpec]] intentionally as this test exercises the top-level + * interface and is more suitable to an Acceptance Testing style. + */ +class FirrtlMainSpec extends FeatureSpec with GivenWhenThen with Matchers with firrtlTests.Utils + with BackendCompilationUtilities { + + /** Parameterizes one test of [[FirrtlMain]]. Running the [[FirrtlMain]] `main` with certain args should produce + * certain files. + * @param args arguments to pass + * @param circuit a [[FirrtlCircuitFixture]] to use. This will generate an appropriate '-i $targetDir/$main.fi' + * argument. + * @param files expected files that will be created + * @param stdout expected stdout string, None if no output expected + * @param stderr expected stderr string, None if no output expected + * @param result expected exit code + */ + case class FirrtlMainTest( + args: Array[String], + circuit: Option[FirrtlCircuitFixture] = Some(new SimpleFirrtlCircuitFixture), + files: Seq[String] = Seq.empty, + stdout: Option[String] = None, + stderr: Option[String] = None, + result: Int = 0) { + /** Generate a name for the test based on the arguments */ + def testName: String = "args" + args.mkString("_") + + /** Print the arguments as a single string */ + def argsString: String = args.mkString(" ") + } + + /** Run the FIRRTL stage with some command line arguments expecting output files to be created. The target directory is + * implied, but will be created by the stage. + * @param p some test parameters + */ + def runStageExpectFiles(p: FirrtlMainTest): Unit = { + scenario(s"""User runs FIRRTL Stage with '${p.argsString}'""") { + val f = new FirrtlMainFixture + val td = new TargetDirectoryFixture(p.testName) + + val inputFile: Array[String] = p.circuit match { + case Some(c) => + And("some input FIRRTL IR") + val in = new File(td.dir, c.main) + val pw = new PrintWriter(in) + pw.write(c.input) + pw.close() + Array("-i", in.toString) + case None => Array.empty + } + + p.files.foreach( f => new File(td.buildDir + s"/$f").delete() ) + + When(s"""the user tries to compile with '${p.argsString}'""") + val (stdout, stderr, result) = + grabStdOutErr { catchStatus { f.stage.main(inputFile ++ Array("-td", td.buildDir.toString) ++ p.args) } } + + p.stdout match { + case Some(a) => + Then(s"""STDOUT should include "$a"""") + stdout should include (a) + case None => + Then(s"nothing should print to STDOUT") + stdout should be (empty) + } + + p.stderr match { + case Some(a) => + And(s"""STDERR should include "$a"""") + stderr should include (a) + case None => + And(s"nothing should print to STDERR") + stderr should be (empty) + } + + p.result match { + case 0 => + And(s"the exit code should be 0") + result shouldBe a [Right[_,_]] + case a => + And(s"the exit code should be $a") + result shouldBe (Left(a)) + } + + p.files.foreach { f => + And(s"file '$f' should be emitted in the target directory") + val out = new File(td.buildDir + s"/$f") + out should (exist) + } + } + } + + + /** Test fixture that links to the [[FirrtlMain]] object. This could be done without, but its use matches the + * Given/When/Then style more accurately. + */ + class FirrtlMainFixture { + Given("the FIRRTL stage") + val stage = FirrtlMain + } + + /** Test fixture that creates a build directory + * @param dirName the name of the base directory; a `build` directory is created under this + */ + class TargetDirectoryFixture(dirName: String) { + val dir = new File(s"test_run_dir/FirrtlMainSpec/$dirName") + val buildDir = new File(dir + "/build") + dir.mkdirs() + } + + trait FirrtlCircuitFixture { + val main: String + val input: String + } + + /** Test fixture defining a simple FIRRTL circuit that will emit differently with and without `--split-modules`. */ + class SimpleFirrtlCircuitFixture extends FirrtlCircuitFixture { + val main: String = "Top" + val input: String = + """|circuit Top: + | module Top: + | output foo: UInt<32> + | inst c of Child + | inst e of External + | foo <= tail(add(c.foo, e.foo), 1) + | module Child: + | output foo: UInt<32> + | inst e of External + | foo <= e.foo + | extmodule External: + | output foo: UInt<32> + |""".stripMargin + } + + info("As a FIRRTL command line user") + info("I want to compile some FIRRTL") + feature("FirrtlMain command line interface") { + scenario("User tries to discover available options") { + val f = new FirrtlMainFixture + + When("the user passes '--help'") + /* Note: THIS CANNOT CATCH THE STATUS BECAUSE SCOPT CATCHES ALL THROWABLE!!! The catchStatus is only used to prevent + * sys.exit from killing the test. However, this should additionally return an exit code of 0 and not print an + * error. The nature of running this through catchStatus causes scopt to intercept the custom SecurityException + * and then use that as evidence to exit with a code of 1. + */ + val (out, _, result) = grabStdOutErr { catchStatus { f.stage.main(Array("--help")) } } + + Then("the usage text should be shown") + out should include ("Usage: firrtl") + + And("usage text should show known registered transforms") + out should include ("--no-dce") + + And("usage text should show known registered libraries") + out should include ("MemLib Options") + + info("""And the exit code should be 0, but scopt catches all throwable, so we can't check this... ¯\_(ツ)_/¯""") + // And("the exit code should be zero") + // out should be (Left(0)) + } + + Seq( + /* Test all standard emitters with and without annotation file outputs */ + FirrtlMainTest(args = Array("-X", "none", "-E", "chirrtl"), + files = Seq("Top.fir")), + FirrtlMainTest(args = Array("-X", "high", "-E", "high"), + files = Seq("Top.hi.fir")), + FirrtlMainTest(args = Array("-X", "middle", "-E", "middle", "-foaf", "Top"), + files = Seq("Top.mid.fir", "Top.anno.json")), + FirrtlMainTest(args = Array("-X", "low", "-E", "low", "-foaf", "annotations.anno.json"), + files = Seq("Top.lo.fir", "annotations.anno.json")), + FirrtlMainTest(args = Array("-X", "verilog", "-E", "verilog", "-foaf", "foo.anno"), + files = Seq("Top.v", "foo.anno.anno.json")), + FirrtlMainTest(args = Array("-X", "sverilog", "-E", "sverilog", "-foaf", "foo.json"), + files = Seq("Top.sv", "foo.json.anno.json"), + stdout = Some("SystemVerilog Compiler behaves the same as the Verilog Compiler!")), + + /* Test all one file per module emitters */ + FirrtlMainTest(args = Array("-X", "none", "-e", "chirrtl"), + files = Seq("Top.fir", "Child.fir")), + FirrtlMainTest(args = Array("-X", "high", "-e", "high"), + files = Seq("Top.hi.fir", "Child.hi.fir")), + FirrtlMainTest(args = Array("-X", "middle", "-e", "middle"), + files = Seq("Top.mid.fir", "Child.mid.fir")), + FirrtlMainTest(args = Array("-X", "low", "-e", "low"), + files = Seq("Top.lo.fir", "Child.lo.fir")), + FirrtlMainTest(args = Array("-X", "verilog", "-e", "verilog"), + files = Seq("Top.v", "Child.v")), + FirrtlMainTest(args = Array("-X", "sverilog", "-e", "sverilog"), + files = Seq("Top.sv", "Child.sv"), + stdout = Some("SystemVerilog Compiler behaves the same as the Verilog Compiler!")), + + /* Test changes to output file name */ + FirrtlMainTest(args = Array("-X", "none", "-E", "chirrtl", "-o", "foo"), + files = Seq("foo.fir")), + FirrtlMainTest(args = Array("-X", "high", "-E", "high", "-o", "foo"), + files = Seq("foo.hi.fir")), + FirrtlMainTest(args = Array("-X", "middle", "-E", "middle", "-o", "foo.middle"), + files = Seq("foo.middle.mid.fir")), + FirrtlMainTest(args = Array("-X", "low", "-E", "low", "-o", "foo.lo.fir"), + files = Seq("foo.lo.fir")), + FirrtlMainTest(args = Array("-X", "verilog", "-E", "verilog", "-o", "foo.sv"), + files = Seq("foo.sv.v")), + FirrtlMainTest(args = Array("-X", "sverilog", "-E", "sverilog", "-o", "Foo"), + files = Seq("Foo.sv"), + stdout = Some("SystemVerilog Compiler behaves the same as the Verilog Compiler!")) + ) + .foreach(runStageExpectFiles) + + scenario("User doesn't specify a target directory") { + val f = new FirrtlMainFixture + + When("the user doesn't specify a target directory") + val outName = "FirrtlMainSpecNoTargetDirectory" + val out = new File(s"$outName.hi.fir") + out.delete() + val result = catchStatus { + f.stage.main(Array("-i", "src/test/resources/integration/GCDTester.fir", "-o", outName, "-X", "high", + "-E", "high")) } + + Then("outputs should be written to current directory") + out should (exist) + out.delete() + } + + scenario("User provides Protocol Buffer input") { + val f = new FirrtlMainFixture + val td = new TargetDirectoryFixture("protobuf-works") + + And("some Protocol Buffer input") + val protobufIn = new File(td.dir + "/Foo.pb") + copyResourceToFile("/integration/GCDTester.pb", protobufIn) + + When("the user tries to compile to High FIRRTL") + f.stage.main(Array("-i", protobufIn.toString, "-X", "high", "-E", "high", "-td", td.buildDir.toString, + "-o", "Foo")) + + Then("the output should be the same as using FIRRTL input") + new File(td.buildDir + "/Foo.hi.fir") should (exist) + } + + } + + info("As a FIRRTL command line user") + info("I want to receive error messages when I do not specify mandatory inputs") + feature("FirrtlMain input validation of mandatory options") { + scenario("User gives no command line options (no input circuit specified)") { + val f = new FirrtlMainFixture + + When("the user passes no arguments") + val (out, err, result) = grabStdOutErr { catchStatus { f.stage.main(Array.empty) } } + + Then("an error should be printed on stdout") + out should include (s"Error: Unable to determine FIRRTL source to read") + + And("no usage text should be shown") + out should not include ("Usage: firrtl") + + And("nothing should print to stderr") + err should be (empty) + + And("the exit code should be 1") + result should be (Left(1)) + } + } + + info("As a FIRRTL command line user") + info("I want to receive helpful error and warnings message") + feature("FirrtlMain input validation") { + /* Note: most input validation occurs inside firrtl.stage.phases.Checks. This seeks to validate command line + * behavior. + */ + + scenario("User tries to use an implicit annotation file") { + val f = new FirrtlMainFixture + val td = new TargetDirectoryFixture("implict-annotation-file") + val circuit = new SimpleFirrtlCircuitFixture + + And("implicit legacy and extant annotation files") + val annoFiles = Array( (new File(td.dir + "/Top.anno"), "/annotations/SampleAnnotations.anno"), + (new File(td.dir + "/Top.anno.json"), "/annotations/SampleAnnotations.anno.json") ) + annoFiles.foreach{ case (file, source) => copyResourceToFile(source, file) } + + When("the user implies an annotation file (an annotation file has the same base name as an input file)") + val in = new File(td.dir + "/Top.fir") + val pw = new PrintWriter(in) + pw.write(circuit.input) + pw.close() + val (out, _, result) = grabStdOutErr{ catchStatus { f.stage.main(Array("-td", td.dir.toString, + "-i", in.toString, + "-foaf", "Top.out", + "-X", "high", + "-E", "high")) } } + + Then("the implicit annotation file should NOT be read") + val annoFileOut = new File(td.dir + "/Top.out.anno.json") + val annotationJson = Source.fromFile(annoFileOut).mkString + annotationJson should not include ("InlineInstances") + + And("no warning should be printed") + out should not include ("Warning:") + + And("no error should be printed") + out should not include ("Error:") + + And("the exit code should be 0") + result shouldBe a [Right[_,_]] + } + + scenario("User provides unsupported legacy annotations") { + val f = new FirrtlMainFixture + val td = new TargetDirectoryFixture("legacy-annotation-file") + val circuit = new SimpleFirrtlCircuitFixture + + And("a legacy annotation file") + val annoFile = new File(td.dir + "/legacy.anno") + copyResourceToFile("/annotations/SampleAnnotations.anno", annoFile) + + When("the user provides legacy annotations") + val in = new File(td.dir + "/Top.fir") + val pw = new PrintWriter(in) + pw.write(circuit.input) + pw.close() + val (out, _, result) = grabStdOutErr{ catchStatus { f.stage.main(Array("-td", td.dir.toString, + "-i", in.toString, + "-faf", annoFile.toString, + "-foaf", "Top", + "-X", "high")) } } + + Then("a warning should be printed") + out should include ("YAML Annotation files are deprecated") + + And("the exit code should be 0") + result shouldBe a [Right[_,_]] + } + + Seq( + /* Erroneous inputs */ + FirrtlMainTest(args = Array("--thisIsNotASupportedOption"), + circuit = None, + stdout = Some("Error: Unknown option"), + result = 1), + FirrtlMainTest(args = Array("-i", "foo", "--info-mode", "Use"), + circuit = None, + stdout = Some("Unknown info mode 'Use'! (Did you misspell it?)"), + result = 1), + FirrtlMainTest(args = Array("-i", "test_run_dir/I-DO-NOT-EXIST"), + circuit = None, + stdout = Some("Input file 'test_run_dir/I-DO-NOT-EXIST' not found!"), + result = 1), + FirrtlMainTest(args = Array("-i", "foo", "-X", "Verilog"), + circuit = None, + stdout = Some("Unknown compiler name 'Verilog'! (Did you misspell it?)"), + result = 1) + ) + .foreach(runStageExpectFiles) + + } + + info("As a FIRRTL transform developer") + info("I want to register my custom transforms with FIRRTL") + feature("FirrtlMain transform registration") { + scenario("User doesn't know if their transforms were registered") { + val f = new FirrtlMainFixture + + When("the user passes '--show-registrations'") + val (out, _, result) = grabStdOutErr { catchStatus { f.stage.main(Array("--show-registrations")) } } + + Then("stdout should show registered transforms") + out should include ("firrtl.passes.InlineInstances") + + And("stdout should show registered libraries") + out should include("firrtl.passes.memlib.MemLibOptions") + + And("the exit code should be 1") + result should be (Left(1)) + } + } + + info("As a longtime FIRRTL user") + info("I migrate from Driver to FirrtlMain") + feature("FirrtlMain migration helpers") { + def optionRemoved(a: String): Option[String] = Some(s"Option '$a' was removed as part of the FIRRTL Stage refactor") + Seq( + /* Removed --top-name/-tn handling */ + FirrtlMainTest(args = Array("--top-name", "foo"), + circuit = None, + stdout = optionRemoved("--top-name/-tn"), + result = 1), + FirrtlMainTest(args = Array("-tn"), + circuit = None, + stdout = optionRemoved("--top-name/-tn"), + result = 1), + /* Removed --split-modules/-fsm handling */ + FirrtlMainTest(args = Array("--split-modules"), + circuit = None, + stdout = optionRemoved("--split-modules/-fsm"), + result = 1), + FirrtlMainTest(args = Array("-fsm"), + circuit = None, + stdout = optionRemoved("--split-modules/-fsm"), + result = 1) + ) + .foreach(runStageExpectFiles) + } +} diff --git a/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala b/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala new file mode 100644 index 00000000..cf8bfb26 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/FirrtlOptionsViewSpec.scala @@ -0,0 +1,70 @@ +// See LICENSE for license details. + +package firrtlTests.stage + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.options._ +import firrtl.stage._ + +import firrtl.{CircuitForm, CircuitState, ir, NoneCompiler, Parser, UnknownForm} +import firrtl.options.Viewer.view +import firrtl.stage.{FirrtlOptions, FirrtlOptionsView} + +class BazCompiler extends NoneCompiler + +class Baz_Compiler extends NoneCompiler + +class FirrtlOptionsViewSpec extends FlatSpec with Matchers { + + behavior of FirrtlOptionsView.getClass.getName + + def circuitString(main: String): String = s"""|circuit $main: + | module $main: + | node x = UInt<1>("h0") + |""".stripMargin + + val corge: String = circuitString("corge") + + val grault: ir.Circuit = Parser.parse(circuitString("grault")) + + val annotations = Seq( + /* FirrtlOptions */ + OutputFileAnnotation("bar"), + CompilerAnnotation(new BazCompiler()), + InfoModeAnnotation("use"), + FirrtlCircuitAnnotation(grault) + ) + + it should "construct a view from an AnnotationSeq" in { + val out = view[FirrtlOptions](annotations) + + out.outputFileName should be (Some("bar")) + out.compiler.getClass should be (classOf[BazCompiler]) + out.infoModeName should be ("use") + out.firrtlCircuit should be (Some(grault)) + } + + /* This test only exists to catch changes to existing behavior. This test does not indicate that this is the correct + * behavior, only that modifications to existing code will not change behavior that people may expect. + */ + it should "overwrite or append to earlier annotation information with later annotation information" in { + val corge_ = circuitString("xyzzy_") + val grault_ = Parser.parse(circuitString("thud_")) + + val overwrites = Seq( + OutputFileAnnotation("bar_"), + CompilerAnnotation(new Baz_Compiler()), + InfoModeAnnotation("gen"), + FirrtlCircuitAnnotation(grault_) + ) + + val out = view[FirrtlOptions](annotations ++ overwrites) + + out.outputFileName should be (Some("bar_")) + out.compiler.getClass should be (classOf[Baz_Compiler]) + out.infoModeName should be ("gen") + out.firrtlCircuit should be (Some(grault_)) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala new file mode 100644 index 00000000..a86c5f12 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/AddCircuitSpec.scala @@ -0,0 +1,101 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.{AnnotationSeq, Parser} +import firrtl.annotations.NoTargetAnnotation +import firrtl.options.{OptionsException, Phase, PhasePrerequisiteException} +import firrtl.stage.{CircuitOption, FirrtlCircuitAnnotation, FirrtlSourceAnnotation, InfoModeAnnotation, + FirrtlFileAnnotation} +import firrtl.stage.phases.AddCircuit + +import java.io.{File, FileWriter} + +class AddCircuitSpec extends FlatSpec with Matchers { + + case class FooAnnotation(x: Int) extends NoTargetAnnotation + case class BarAnnotation(x: String) extends NoTargetAnnotation + + class Fixture { val phase: Phase = new AddCircuit } + + behavior of classOf[AddCircuit].toString + + def firrtlSource(name: String): String = + s"""|circuit $name: + | module $name: + | input a: UInt<1> + | output b: UInt<1> + | b <= not(a) + |""".stripMargin + + it should "throw a PhasePrerequisiteException if a CircuitOption exists without an InfoModeAnnotation" in + new Fixture { + {the [PhasePrerequisiteException] thrownBy phase.transform(Seq(FirrtlSourceAnnotation("foo")))} + .message should startWith ("An InfoModeAnnotation must be present") + } + + it should "do nothing if no CircuitOption annotations are present" in new Fixture { + val annotations = (1 to 10).map(FooAnnotation) ++ + ('a' to 'm').map(_.toString).map(BarAnnotation) :+ InfoModeAnnotation("ignore") + phase.transform(annotations).toSeq should be (annotations.toSeq) + } + + val (file, fileCircuit) = { + val source = firrtlSource("foo") + val fileName = "test_run_dir/AddCircuitSpec.fir" + val fw = new FileWriter(new File(fileName)) + fw.write(source) + fw.close() + (fileName, Parser.parse(source)) + } + + val (source, sourceCircuit) = { + val source = firrtlSource("bar") + (source, Parser.parse(source)) + } + + it should "transform and remove CircuitOption annotations" in new Fixture { + val circuit = Parser.parse(firrtlSource("baz")) + + val annotations = Seq( + FirrtlFileAnnotation(file), + FirrtlSourceAnnotation(source), + FirrtlCircuitAnnotation(circuit), + InfoModeAnnotation("ignore") ) + + val annotationsExpected = Set( + FirrtlCircuitAnnotation(fileCircuit), + FirrtlCircuitAnnotation(sourceCircuit), + FirrtlCircuitAnnotation(circuit) ) + + val out = phase.transform(annotations).toSeq + + info("generated expected FirrtlCircuitAnnotations") + out.collect{ case a: FirrtlCircuitAnnotation => a}.toSet should be (annotationsExpected) + + info("all CircuitOptions were removed") + out.collect{ case a: CircuitOption => a } should be (empty) + } + + it should """add info for a FirrtlFileAnnotation with a "gen" info mode""" in new Fixture { + phase.transform(Seq(InfoModeAnnotation("gen"), FirrtlFileAnnotation(file))) + .collectFirst{ case a: FirrtlCircuitAnnotation => a.circuit.serialize } + .get should include ("AddCircuitSpec") + } + + it should """add info for a FirrtlSourceAnnotation with an "append" info mode""" in new Fixture { + phase.transform(Seq(InfoModeAnnotation("append"), FirrtlSourceAnnotation(source))) + .collectFirst{ case a: FirrtlCircuitAnnotation => a.circuit.serialize } + .get should include ("anonymous source") + } + + it should "throw an OptionsException if the specified file doesn't exist" in new Fixture { + val a = Seq(InfoModeAnnotation("ignore"), FirrtlFileAnnotation("test_run_dir/I-DO-NOT-EXIST")) + + {the [OptionsException] thrownBy phase.transform(a)} + .message should startWith (s"Input file 'test_run_dir/I-DO-NOT-EXIST' not found") + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala new file mode 100644 index 00000000..c4566dd5 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/AddDefaultsSpec.scala @@ -0,0 +1,37 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.NoneCompiler +import firrtl.annotations.Annotation +import firrtl.stage.phases.AddDefaults +import firrtl.transforms.BlackBoxTargetDirAnno +import firrtl.stage.{CompilerAnnotation, InfoModeAnnotation} +import firrtl.options.{Phase, TargetDirAnnotation} + +class AddDefaultsSpec extends FlatSpec with Matchers { + + class Fixture { val phase: Phase = new AddDefaults } + + behavior of classOf[AddDefaults].toString + + it should "add expected default annotations and nothing else" in new Fixture { + val expected = Seq( + (a: Annotation) => a match { case BlackBoxTargetDirAnno(b) => b == TargetDirAnnotation().directory }, + (a: Annotation) => a match { case CompilerAnnotation(b) => b.getClass == CompilerAnnotation().compiler.getClass }, + (a: Annotation) => a match { case InfoModeAnnotation(b) => b == InfoModeAnnotation().modeName } ) + + phase.transform(Seq.empty).zip(expected).map { case (x, f) => f(x) should be (true) } + } + + it should "not overwrite existing annotations" in new Fixture { + val input = Seq( + BlackBoxTargetDirAnno("foo"), + CompilerAnnotation(new NoneCompiler()), + InfoModeAnnotation("ignore")) + + phase.transform(input).toSeq should be (input) + } +} diff --git a/src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala new file mode 100644 index 00000000..117e9c5f --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/AddImplicitEmitterSpec.scala @@ -0,0 +1,45 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.{EmitAllModulesAnnotation, EmitCircuitAnnotation, HighFirrtlEmitter, VerilogCompiler} +import firrtl.annotations.NoTargetAnnotation +import firrtl.options.Phase +import firrtl.stage.{CompilerAnnotation, RunFirrtlTransformAnnotation} +import firrtl.stage.phases.AddImplicitEmitter + +class AddImplicitEmitterSpec extends FlatSpec with Matchers { + + case class FooAnnotation(x: Int) extends NoTargetAnnotation + case class BarAnnotation(x: String) extends NoTargetAnnotation + + class Fixture { val phase: Phase = new AddImplicitEmitter } + + val someAnnos = Seq(FooAnnotation(1), FooAnnotation(2), BarAnnotation("bar")) + + behavior of classOf[AddImplicitEmitter].toString + + it should "do nothing if no CompilerAnnotation is present" in new Fixture { + phase.transform(someAnnos).toSeq should be (someAnnos) + } + + it should "add an EmitCircuitAnnotation derived from a CompilerAnnotation" in new Fixture { + val input = CompilerAnnotation(new VerilogCompiler) +: someAnnos + val expected = input.flatMap{ + case a@ CompilerAnnotation(b) => Seq(a, + RunFirrtlTransformAnnotation(b.emitter), + EmitCircuitAnnotation(b.emitter.getClass)) + case a => Some(a) + } + phase.transform(input).toSeq should be (expected) + } + + it should "not add an EmitCircuitAnnotation if an EmitAnnotation already exists" in new Fixture { + val input = Seq(CompilerAnnotation(new VerilogCompiler), + EmitAllModulesAnnotation(classOf[HighFirrtlEmitter])) ++ someAnnos + phase.transform(input).toSeq should be (input) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala b/src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala new file mode 100644 index 00000000..72d186b5 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/AddImplicitOutputFileSpec.scala @@ -0,0 +1,46 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.{ChirrtlEmitter, EmitAllModulesAnnotation, Parser} +import firrtl.options.Phase +import firrtl.stage.{FirrtlCircuitAnnotation, OutputFileAnnotation} +import firrtl.stage.phases.AddImplicitOutputFile + +class AddImplicitOutputFileSpec extends FlatSpec with Matchers { + + class Fixture { val phase: Phase = new AddImplicitOutputFile } + + val foo = """|circuit Foo: + | module Foo: + | node a = UInt<1>("h0") + |""".stripMargin + + val circuit = Parser.parse(foo) + + behavior of classOf[AddImplicitOutputFile].toString + + it should "default to an output file named 'a'" in new Fixture { + phase.transform(Seq.empty).toSeq should be (Seq(OutputFileAnnotation("a"))) + } + + it should "set the output file based on a FirrtlCircuitAnnotation's main" in new Fixture { + val in = Seq(FirrtlCircuitAnnotation(circuit)) + val out = OutputFileAnnotation(circuit.main) +: in + phase.transform(in).toSeq should be (out) + } + + it should "do nothing if an OutputFileAnnotation or EmitAllModulesAnnotation already exists" in new Fixture { + + info("OutputFileAnnotation works") + val outputFile = Seq(OutputFileAnnotation("Bar"), FirrtlCircuitAnnotation(circuit)) + phase.transform(outputFile).toSeq should be (outputFile) + + info("EmitAllModulesAnnotation works") + val eam = Seq(EmitAllModulesAnnotation(classOf[ChirrtlEmitter]), FirrtlCircuitAnnotation(circuit)) + phase.transform(eam).toSeq should be (eam) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala b/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala new file mode 100644 index 00000000..47aa9b5b --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/ChecksSpec.scala @@ -0,0 +1,89 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.stage._ + +import firrtl.{AnnotationSeq, ChirrtlEmitter, EmitAllModulesAnnotation, NoneCompiler} +import firrtl.options.{OptionsException, OutputAnnotationFileAnnotation, Phase} +import firrtl.stage.phases.{AddImplicitOutputFile, Checks} + +class ChecksSpec extends FlatSpec with Matchers { + + class Fixture { val phase: Phase = new Checks } + + val inputFile = FirrtlFileAnnotation("foo") + val outputFile = OutputFileAnnotation("bar") + val emitAllModules = EmitAllModulesAnnotation(classOf[ChirrtlEmitter]) + val outputAnnotationFile = OutputAnnotationFileAnnotation("baz") + val goodCompiler = CompilerAnnotation(new NoneCompiler()) + val infoMode = InfoModeAnnotation("ignore") + + val min = Seq(inputFile, goodCompiler, infoMode) + + def checkExceptionMessage(phase: Phase, annotations: AnnotationSeq, messageStart: String): Unit = + intercept[OptionsException]{ phase.transform(annotations) }.getMessage should startWith(messageStart) + + behavior of classOf[Checks].toString + + it should "require exactly one input source" in new Fixture { + info("0 input source causes an exception") + checkExceptionMessage(phase, Seq.empty, "Unable to determine FIRRTL source to read") + + info("2 input sources causes an exception") + val in = min :+ FirrtlSourceAnnotation("circuit Foo:") + checkExceptionMessage(phase, in, "Multiply defined input FIRRTL sources") + } + + it should "require mutual exclusivity of OutputFileAnnotation and EmitAllModulesAnnotation" in new Fixture { + info("OutputFileAnnotation alone works") + phase.transform(min :+ outputFile) + + info("OneFilePerModuleAnnotation alone works") + phase.transform(min :+ emitAllModules) + + info("Together, they throw an exception") + val in = min ++ Seq(outputFile, emitAllModules) + checkExceptionMessage(phase, in, "Output file is incompatible with emit all modules annotation") + } + + it should "enforce zero or one output files" in new Fixture { + val in = min ++ Seq(outputFile, outputFile) + checkExceptionMessage(phase, in, "No more than one output file can be specified") + } + + it should "enforce exactly one compiler" in new Fixture { + info("0 compilers should throw an exception") + val inZero = Seq(inputFile, infoMode) + checkExceptionMessage(phase, inZero, "Exactly one compiler must be specified, but none found") + + info("2 compilers should throw an exception") + val c = goodCompiler.compiler + val inTwo = min :+ goodCompiler + checkExceptionMessage(phase, inTwo, s"Exactly one compiler must be specified, but found '$c, $c'") + } + + it should "validate info mode names" in new Fixture { + info("Good info mode names should work") + Seq("ignore", "use", "gen", "append") + .map(info => phase.transform(Seq(inputFile, goodCompiler, InfoModeAnnotation(info)))) + } + + it should "enforce exactly one info mode" in new Fixture { + info("0 info modes should throw an exception") + checkExceptionMessage(phase, Seq(inputFile, goodCompiler), + "Exactly one info mode must be specified, but none found") + + info("2 info modes should throw an exception") + val i = infoMode.modeName + checkExceptionMessage(phase, min :+ infoMode, s"Exactly one info mode must be specified, but found '$i, $i'") + } + + it should "pass if the minimum annotations are specified" in new Fixture { + info(s"""Minimum required: ${min.map(_.getClass.getSimpleName).mkString(", ")}""") + phase.transform(min) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala b/src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala new file mode 100644 index 00000000..7a3ab6b7 --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/CompilerSpec.scala @@ -0,0 +1,143 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import firrtl.{Compiler => _, _} +import firrtl.options.{Phase, PhasePrerequisiteException} +import firrtl.stage.{CompilerAnnotation, FirrtlCircuitAnnotation, RunFirrtlTransformAnnotation} +import firrtl.stage.phases.Compiler + +class CompilerSpec extends FlatSpec with Matchers { + + class Fixture { val phase: Phase = new Compiler } + + behavior of classOf[Compiler].toString + + it should "do nothing for an empty AnnotationSeq" in new Fixture { + phase.transform(Seq.empty).toSeq should be (empty) + } + + /** A circuit with a parameterized main (top name) that is different at High, Mid, and Low FIRRTL forms. */ + private def chirrtl(main: String): String = + s"""|circuit $main: + | module $main: + | output foo: {bar: UInt} + | + | foo.bar <= UInt<4>("h0") + |""".stripMargin + + it should "compile a circuit to Low FIRRTL when using the Verilog compiler" in new Fixture { + val compiler = new VerilogCompiler + + val circuitIn = Parser.parse(chirrtl("top")) + val circuitOut = compiler.compile(CircuitState(circuitIn, ChirrtlForm), Seq.empty).circuit + + val input = Seq( + FirrtlCircuitAnnotation(circuitIn), + CompilerAnnotation(compiler) ) + + val expected = Seq(FirrtlCircuitAnnotation(circuitOut)) + + phase.transform(input).toSeq should be (expected) + } + + it should "compile multiple FirrtlCircuitAnnotations" in new Fixture { + val (nc, hfc, mfc, lfc, vc, svc) = ( + new NoneCompiler, + new HighFirrtlCompiler, + new MiddleFirrtlCompiler, + new LowFirrtlCompiler, + new VerilogCompiler, + new SystemVerilogCompiler ) + val (ce, hfe, mfe, lfe, ve, sve) = ( + new ChirrtlEmitter, + new HighFirrtlEmitter, + new MiddleFirrtlEmitter, + new LowFirrtlEmitter, + new VerilogEmitter, + new SystemVerilogEmitter ) + + val a = Seq( + /* Default Compiler is HighFirrtlCompiler */ + CompilerAnnotation(hfc), + + /* First compiler group, use NoneCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("a"))), + CompilerAnnotation(nc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + + /* Second compiler group, use default HighFirrtlCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("b"))), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + + /* Third compiler group, use MiddleFirrtlCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("c"))), + CompilerAnnotation(mfc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + RunFirrtlTransformAnnotation(mfe), + EmitCircuitAnnotation(mfe.getClass), + + /* Fourth compiler group, use LowFirrtlCompiler*/ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("d"))), + CompilerAnnotation(lfc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + RunFirrtlTransformAnnotation(mfe), + EmitCircuitAnnotation(mfe.getClass), + RunFirrtlTransformAnnotation(lfe), + EmitCircuitAnnotation(lfe.getClass), + + /* Fifth compiler group, use VerilogCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("e"))), + CompilerAnnotation(vc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + RunFirrtlTransformAnnotation(mfe), + EmitCircuitAnnotation(mfe.getClass), + RunFirrtlTransformAnnotation(lfe), + EmitCircuitAnnotation(lfe.getClass), + RunFirrtlTransformAnnotation(ve), + EmitCircuitAnnotation(ve.getClass), + + /* Sixth compiler group, use SystemVerilogCompiler */ + FirrtlCircuitAnnotation(Parser.parse(chirrtl("f"))), + CompilerAnnotation(svc), + RunFirrtlTransformAnnotation(ce), + EmitCircuitAnnotation(ce.getClass), + RunFirrtlTransformAnnotation(hfe), + EmitCircuitAnnotation(hfe.getClass), + RunFirrtlTransformAnnotation(mfe), + EmitCircuitAnnotation(mfe.getClass), + RunFirrtlTransformAnnotation(lfe), + EmitCircuitAnnotation(lfe.getClass), + RunFirrtlTransformAnnotation(sve), + EmitCircuitAnnotation(sve.getClass) + ) + + val output = phase.transform(a) + + info("with the same number of output FirrtlCircuitAnnotations") + output + .collect{ case a: FirrtlCircuitAnnotation => a } + .size should be (6) + + info("and all expected EmittedAnnotations should be generated") + output + .collect{ case a: EmittedAnnotation[_] => a } + .size should be (20) + } + +} diff --git a/src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala b/src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala new file mode 100644 index 00000000..4f7ad92a --- /dev/null +++ b/src/test/scala/firrtlTests/stage/phases/WriteEmittedSpec.scala @@ -0,0 +1,79 @@ +// See LICENSE for license details. + +package firrtlTests.stage.phases + +import org.scalatest.{FlatSpec, Matchers} + +import java.io.File + +import firrtl._ + +import firrtl.options.{Phase, TargetDirAnnotation} +import firrtl.stage.OutputFileAnnotation +import firrtl.stage.phases.WriteEmitted + +class WriteEmittedSpec extends FlatSpec with Matchers { + + def removeEmitted(a: AnnotationSeq): AnnotationSeq = a.flatMap { + case a: EmittedAnnotation[_] => None + case a => Some(a) + } + + class Fixture { val phase: Phase = new WriteEmitted } + + behavior of classOf[WriteEmitted].toString + + it should "write emitted circuits" in new Fixture { + val annotations = Seq( + TargetDirAnnotation("test_run_dir/WriteEmittedSpec"), + EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit("foo", "", ".foocircuit")), + EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit("bar", "", ".barcircuit")), + EmittedVerilogCircuitAnnotation(EmittedVerilogCircuit("baz", "", ".bazcircuit")) ) + val expected = Seq("foo.foocircuit", "bar.barcircuit", "baz.bazcircuit") + .map(a => new File(s"test_run_dir/WriteEmittedSpec/$a")) + + info("annotations are unmodified") + phase.transform(annotations).toSeq should be (removeEmitted(annotations).toSeq) + + expected.foreach{ a => + info(s"$a was written") + a should (exist) + a.delete() + } + } + + it should "default to the output file name if one exists" in new Fixture { + val annotations = Seq( + TargetDirAnnotation("test_run_dir/WriteEmittedSpec"), + OutputFileAnnotation("quux"), + EmittedFirrtlCircuitAnnotation(EmittedFirrtlCircuit("qux", "", ".quxcircuit")) ) + val expected = new File("test_run_dir/WriteEmittedSpec/quux.quxcircuit") + + info("annotations are unmodified") + phase.transform(annotations).toSeq should be (removeEmitted(annotations).toSeq) + + info(s"$expected was written") + expected should (exist) + expected.delete() + } + + it should "write emitted modules" in new Fixture { + val annotations = Seq( + TargetDirAnnotation("test_run_dir/WriteEmittedSpec"), + EmittedFirrtlModuleAnnotation(EmittedFirrtlModule("foo", "", ".foomodule")), + EmittedFirrtlModuleAnnotation(EmittedFirrtlModule("bar", "", ".barmodule")), + EmittedVerilogModuleAnnotation(EmittedVerilogModule("baz", "", ".bazmodule")) ) + val expected = Seq("foo.foomodule", "bar.barmodule", "baz.bazmodule") + .map(a => new File(s"test_run_dir/WriteEmittedSpec/$a")) + + info("EmittedComponent annotations are deleted") + phase.transform(annotations).toSeq should be (removeEmitted(annotations).toSeq) + + expected.foreach{ a => + info(s"$a was written") + a should (exist) + a.delete() + } + } + +} -- cgit v1.2.3 From ef8f06f23b9ee6cf86de2450752dfd0fcd32da80 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Mon, 22 Apr 2019 21:20:08 -0400 Subject: Add ShellOption, DeletedWrapper Abstracts away option writing such that users no longer have to understand scopt semantics. This adds a ShellOption class and a HasShellOptions trait for something which provides one or more ShellOptions. This refactors the FIRRTL codebase to use this style of option specification. Adds and uses DeletedWrapper to automatically generate DeletedAnnotations. Signed-off-by: Schuyler Eldridge --- .../firrtlTests/options/RegistrationSpec.scala | 21 ++++++++++++++------- .../firrtlTests/options/phases/ChecksSpec.scala | 4 ++-- .../options/phases/GetIncludesSpec.scala | 1 - 3 files changed, 16 insertions(+), 10 deletions(-) (limited to 'src/test') diff --git a/src/test/scala/firrtlTests/options/RegistrationSpec.scala b/src/test/scala/firrtlTests/options/RegistrationSpec.scala index c060341d..43d71b6d 100644 --- a/src/test/scala/firrtlTests/options/RegistrationSpec.scala +++ b/src/test/scala/firrtlTests/options/RegistrationSpec.scala @@ -6,7 +6,7 @@ import org.scalatest.{FlatSpec, Matchers} import scopt.OptionParser import java.util.ServiceLoader -import firrtl.options.{RegisteredTransform, RegisteredLibrary} +import firrtl.options.{RegisteredTransform, RegisteredLibrary, ShellOption} import firrtl.passes.Pass import firrtl.ir.Circuit import firrtl.annotations.NoTargetAnnotation @@ -16,16 +16,23 @@ case object HelloAnnotation extends NoTargetAnnotation class FooTransform extends Pass with RegisteredTransform { def run(c: Circuit): Circuit = c - def addOptions(p: OptionParser[AnnotationSeq]): Unit = - p.opt[Unit]("hello") - .action( (_, c) => HelloAnnotation +: c ) + + val options = Seq( + new ShellOption[Unit]( + longOption = "hello", + toAnnotationSeq = _ => Seq(HelloAnnotation), + helpText = "Hello option") ) + } class BarLibrary extends RegisteredLibrary { def name: String = "Bar" - def addOptions(p: OptionParser[AnnotationSeq]): Unit = - p.opt[Unit]("world") - .action( (_, c) => HelloAnnotation +: c ) + + val options = Seq( + new ShellOption[Unit]( + longOption = "world", + toAnnotationSeq = _ => Seq(HelloAnnotation), + helpText = "World option") ) } class RegistrationSpec extends FlatSpec with Matchers { diff --git a/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala b/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala index e04ba554..b988f838 100644 --- a/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala +++ b/src/test/scala/firrtlTests/options/phases/ChecksSpec.scala @@ -13,14 +13,14 @@ class ChecksSpec extends FlatSpec with Matchers { val targetDir = TargetDirAnnotation("foo") val annoOut = OutputAnnotationFileAnnotation("bar") + class Fixture { val phase: Phase = new Checks } + /* A minimum annotation Seq that will pass [[Checks]] */ val min = Seq(targetDir) def checkExceptionMessage(phase: Phase, annotations: AnnotationSeq, messageStart: String): Unit = intercept[OptionsException]{ phase.transform(annotations) }.getMessage should startWith(messageStart) - class Fixture { val phase: Phase = new Checks } - behavior of classOf[Checks].toString it should "enforce exactly one TargetDirAnnotation" in new Fixture { diff --git a/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala b/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala index 5b07d0e0..1007ca8c 100644 --- a/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala +++ b/src/test/scala/firrtlTests/options/phases/GetIncludesSpec.scala @@ -21,7 +21,6 @@ case object E extends NoTargetAnnotation class GetIncludesSpec extends FlatSpec with Matchers with BackendCompilationUtilities with firrtlTests.Utils { - val dir = new File("test_run_dir/GetIncludesSpec") dir.mkdirs() -- cgit v1.2.3