// SPDX-License-Identifier: Apache-2.0 package firrtlTests import firrtl._ import firrtl.transforms._ import firrtl.testutils._ import annotations._ import java.io.File import java.nio.file.Paths import firrtl.options.Dependency import firrtl.stage.FirrtlStage import firrtl.util.BackendCompilationUtilities.createTestDirectory class CheckCombLoopsSpec extends LeanTransformSpec(Seq(Dependency[CheckCombLoops])) { "Loop-free circuit" should "not throw an exception" in { val input = """circuit hasnoloops : | module thru : | input in1 : UInt<1> | input in2 : UInt<1> | output out1 : UInt<1> | output out2 : UInt<1> | out1 <= in1 | out2 <= in2 | module hasnoloops : | input clk : Clock | input a : UInt<1> | output b : UInt<1> | wire x : UInt<1> | inst inner of thru | inner.in1 <= a | x <= inner.out1 | inner.in2 <= x | b <= inner.out2 |""".stripMargin compile(parse(input)) } "Simple combinational loop" should "throw an exception" in { val input = """circuit hasloops : | module hasloops : | input clk : Clock | input a : UInt<1> | input b : UInt<1> | output c : UInt<1> | output d : UInt<1> | wire y : UInt<1> | wire z : UInt<1> | c <= b | z <= y | y <= z | d <= z |""".stripMargin intercept[CheckCombLoops.CombLoopException] { compile(parse(input)) } } "Single-element combinational loop" should "throw an exception" in { val input = """circuit loop : | module loop : | output y : UInt<8> | wire w : UInt<8> | w <= w | y <= w |""".stripMargin intercept[CheckCombLoops.CombLoopException] { compile(parse(input)) } } "Node combinational loop" should "throw an exception" in { val input = """circuit hasloops : | module hasloops : | input clk : Clock | input a : UInt<1> | input b : UInt<1> | output c : UInt<1> | output d : UInt<1> | wire y : UInt<1> | c <= b | node z = and(c,y) | y <= z | d <= z |""".stripMargin intercept[CheckCombLoops.CombLoopException] { compile(parse(input)) } } "Combinational loop through a combinational memory read port" should "throw an exception" in { val input = """circuit hasloops : | module hasloops : | input clk : Clock | input a : UInt<1> | input b : UInt<1> | output c : UInt<1> | output d : UInt<1> | wire y : UInt<1> | wire z : UInt<1> | c <= b | mem m : | data-type => UInt<1> | depth => 2 | read-latency => 0 | write-latency => 1 | reader => r | read-under-write => undefined | m.r.clk <= clk | m.r.addr <= y | m.r.en <= UInt(1) | z <= m.r.data | y <= z | d <= z |""".stripMargin intercept[CheckCombLoops.CombLoopException] { compile(parse(input)) } } "Combination loop through an instance" should "throw an exception" in { val input = """circuit hasloops : | module thru : | input in : UInt<1> | output out : UInt<1> | out <= in | module hasloops : | input clk : Clock | input a : UInt<1> | input b : UInt<1> | output c : UInt<1> | output d : UInt<1> | wire y : UInt<1> | wire z : UInt<1> | c <= b | inst inner of thru | inner.in <= y | z <= inner.out | y <= z | d <= z |""".stripMargin intercept[CheckCombLoops.CombLoopException] { compile(parse(input)) } } "Combinational loop through an annotated ExtModule" should "throw an exception" in { val input = """circuit hasloops : | extmodule blackbox : | input in : UInt<1> | output out : UInt<1> | module hasloops : | input clk : Clock | input a : UInt<1> | input b : UInt<1> | output c : UInt<1> | output d : UInt<1> | wire y : UInt<1> | wire z : UInt<1> | c <= b | inst inner of blackbox | inner.in <= y | z <= inner.out | y <= z | d <= z |""".stripMargin val mt = ModuleTarget("hasloops", "blackbox") val annos = AnnotationSeq(Seq(ExtModulePathAnnotation(mt.ref("in"), mt.ref("out")))) intercept[CheckCombLoops.CombLoopException] { compile(parse(input), annos) } } "Loop-free circuit with ExtModulePathAnnotations" should "not throw an exception" in { val input = """circuit hasnoloops : | extmodule blackbox : | input in1 : UInt<1> | input in2 : UInt<1> | output out1 : UInt<1> | output out2 : UInt<1> | module hasnoloops : | input clk : Clock | input a : UInt<1> | output b : UInt<1> | wire x : UInt<1> | inst inner of blackbox | inner.in1 <= a | x <= inner.out1 | inner.in2 <= x | b <= inner.out2 |""".stripMargin val mt = ModuleTarget("hasnoloops", "blackbox") val annos = AnnotationSeq( Seq( ExtModulePathAnnotation(mt.ref("in1"), mt.ref("out1")), ExtModulePathAnnotation(mt.ref("in2"), mt.ref("out2")) ) ) compile(parse(input), annos) } "Combinational loop through an output RHS reference" should "throw an exception" in { val input = """circuit hasloops : | module thru : | input in : UInt<1> | output tmp : UInt<1> | output out : UInt<1> | tmp <= in | out <= tmp | module hasloops : | input clk : Clock | input a : UInt<1> | input b : UInt<1> | output c : UInt<1> | output d : UInt<1> | wire y : UInt<1> | wire z : UInt<1> | c <= b | inst inner of thru | inner.in <= y | z <= inner.out | y <= z | d <= z |""".stripMargin intercept[CheckCombLoops.CombLoopException] { compile(parse(input)) } } "Multiple simple loops in one SCC" should "throw an exception" in { val input = """circuit hasloops : | module hasloops : | input i : UInt<1> | output o : UInt<1> | wire a : UInt<1> | wire b : UInt<1> | wire c : UInt<1> | wire d : UInt<1> | wire e : UInt<1> | a <= and(c,i) | b <= and(a,d) | c <= b | d <= and(c,e) | e <= b | o <= e |""".stripMargin intercept[CheckCombLoops.CombLoopException] { compile(parse(input)) } } "Circuit" should "create an annotation" in { val input = """circuit hasnoloops : | module thru : | input in1 : UInt<1> | input in2 : UInt<1> | output out1 : UInt<1> | output out2 : UInt<1> | out1 <= in1 | out2 <= in2 | module hasnoloops : | input clk : Clock | input a : UInt<1> | output b : UInt<1> | wire x : UInt<1> | inst inner of thru | inner.in1 <= a | x <= inner.out1 | inner.in2 <= x | b <= inner.out2 |""".stripMargin val cs = compile(parse(input)) val mt = ModuleTarget("hasnoloops", "hasnoloops") val anno = CombinationalPath(mt.ref("b"), Seq(mt.ref("a"))) cs.annotations.contains(anno) should be(true) } } class CheckCombLoopsCommandLineSpec extends FirrtlFlatSpec { val testDir = createTestDirectory("CombLoopChecker") val inputFile = Paths.get(getClass.getResource("/features/HasLoops.fir").toURI()).toFile() val outFile = new File(testDir, "HasLoops.v") val args = Array("-i", inputFile.getAbsolutePath, "-o", outFile.getAbsolutePath, "-X", "verilog") "Combinational loops detection" should "run by default" in { a[CheckCombLoops.CombLoopException] should be thrownBy { (new FirrtlStage).execute(args, Seq()) } } it should "not run when given --no-check-comb-loops option" in { (new FirrtlStage).execute(args :+ "--no-check-comb-loops", Seq()) } }