// SPDX-License-Identifier: Apache-2.0 package firrtlTests import firrtl._ import firrtl.ir._ import firrtl.passes.{CheckHighForm, CheckInitialization, CheckTypes} import firrtl.transforms.{CheckCombLoops, InferResets} import firrtl.testutils._ import firrtl.testutils.FirrtlCheckers._ // TODO // - Test nodes in the connection // - Test with whens (is this allowed?) class InferResetsSpec extends FirrtlFlatSpec { def compile(input: String, compiler: Compiler = new MiddleFirrtlCompiler): CircuitState = compiler.compileAndEmit(CircuitState(parse(input), ChirrtlForm), List.empty) behavior.of("ResetType") val BoolType = UIntType(IntWidth(1)) it should "support casting to other types" in { val result = compile(s""" |circuit top: | module top: | input a : UInt<1> | output v : UInt<1> | output w : SInt<1> | output x : Clock | output y : Fixed<1><<0>> | output z : AsyncReset | wire r : Reset | r <= a | v <= asUInt(r) | w <= asSInt(r) | x <= asClock(r) | y <= asFixedPoint(r, 0) | z <= asAsyncReset(r)""".stripMargin) result should containLine("wire r : UInt<1>") result should containLine("r <= a") result should containLine("v <= asUInt(r)") result should containLine("w <= asSInt(r)") result should containLine("x <= asClock(r)") result should containLine("y <= asSInt(r)") result should containLine("z <= asAsyncReset(r)") } it should "work across Module boundaries" in { val result = compile(s""" |circuit top : | module child : | input clock : Clock | input childReset : Reset | input x : UInt<8> | output z : UInt<8> | reg r : UInt<8>, clock with : (reset => (childReset, UInt(123))) | r <= x | z <= r | module top : | input clock : Clock | input reset : UInt<1> | input x : UInt<8> | output z : UInt<8> | inst c of child | c.clock <= clock | c.childReset <= reset | c.x <= x | z <= c.z |""".stripMargin) result should containTree { case Port(_, "childReset", Input, BoolType) => true } } it should "work across multiple Module boundaries" in { val result = compile(s""" |circuit top : | module child : | input resetIn : Reset | output resetOut : Reset | resetOut <= resetIn | module top : | input clock : Clock | input reset : UInt<1> | input x : UInt<8> | output z : UInt<8> | inst c of child | c.resetIn <= reset | reg r : UInt<8>, clock with : (reset => (c.resetOut, UInt(123))) | r <= x | z <= r |""".stripMargin) result should containTree { case Port(_, "resetIn", Input, BoolType) => true } result should containTree { case Port(_, "resetOut", Output, BoolType) => true } } it should "work in nested and flipped aggregates with regular and partial connect" in { val result = compile( s""" |circuit top : | module top : | output fizz : { flip foo : { a : AsyncReset, flip b: Reset }[2], bar : { a : Reset, flip b: AsyncReset }[2] } | output buzz : { flip foo : { a : AsyncReset, c: UInt<1>, flip b: Reset }[2], bar : { a : Reset, flip b: AsyncReset, c: UInt<8> }[2] } | fizz.bar <= fizz.foo | buzz.bar <- buzz.foo |""".stripMargin, new LowFirrtlCompiler ) result should containTree { case Port(_, "fizz_foo_0_a", Input, AsyncResetType) => true } result should containTree { case Port(_, "fizz_foo_0_b", Output, AsyncResetType) => true } result should containTree { case Port(_, "fizz_foo_1_a", Input, AsyncResetType) => true } result should containTree { case Port(_, "fizz_foo_1_b", Output, AsyncResetType) => true } result should containTree { case Port(_, "fizz_bar_0_a", Output, AsyncResetType) => true } result should containTree { case Port(_, "fizz_bar_0_b", Input, AsyncResetType) => true } result should containTree { case Port(_, "fizz_bar_1_a", Output, AsyncResetType) => true } result should containTree { case Port(_, "fizz_bar_1_b", Input, AsyncResetType) => true } result should containTree { case Port(_, "buzz_foo_0_a", Input, AsyncResetType) => true } result should containTree { case Port(_, "buzz_foo_0_b", Output, AsyncResetType) => true } result should containTree { case Port(_, "buzz_foo_1_a", Input, AsyncResetType) => true } result should containTree { case Port(_, "buzz_foo_1_b", Output, AsyncResetType) => true } result should containTree { case Port(_, "buzz_bar_0_a", Output, AsyncResetType) => true } result should containTree { case Port(_, "buzz_bar_0_b", Input, AsyncResetType) => true } result should containTree { case Port(_, "buzz_bar_1_a", Output, AsyncResetType) => true } result should containTree { case Port(_, "buzz_bar_1_b", Input, AsyncResetType) => true } } it should "not crash if a ResetType has no drivers" in { a[CheckInitialization.RefNotInitializedException] shouldBe thrownBy { compile(s""" |circuit test : | module test : | output out : Reset | wire w : Reset | out <= w | out <= UInt(1) |""".stripMargin) } } it should "NOT allow last connect semantics to pick the right type for Reset" in { an[InferResets.InferResetsException] shouldBe thrownBy { compile(s""" |circuit top : | module top : | input reset0 : AsyncReset | input reset1 : UInt<1> | output out : Reset | wire w0 : Reset | wire w1 : Reset | w0 <= reset0 | w1 <= reset1 | out <= w0 | out <= w1 |""".stripMargin) } } it should "NOT support last connect semantics across whens" in { an[InferResets.InferResetsException] shouldBe thrownBy { compile(s""" |circuit top : | module top : | input reset0 : AsyncReset | input reset1 : AsyncReset | input reset2 : UInt<1> | input en : UInt<1> | output out : Reset | wire w0 : Reset | wire w1 : Reset | wire w2 : Reset | w0 <= reset0 | w1 <= reset1 | w2 <= reset2 | out <= w2 | when en : | out <= w0 | else : | out <= w1 |""".stripMargin) } } it should "not allow different Reset Types to drive a single Reset" in { an[InferResets.InferResetsException] shouldBe thrownBy { val result = compile(s""" |circuit top : | module top : | input reset0 : AsyncReset | input reset1 : UInt<1> | input en : UInt<1> | output out : Reset | wire w1 : Reset | wire w2 : Reset | w1 <= reset0 | w2 <= reset1 | out <= w1 | when en : | out <= w2 |""".stripMargin) } } it should "allow concrete reset types to overrule invalidation" in { val result = compile(s""" |circuit test : | module test : | input in : AsyncReset | output out : Reset | out is invalid | out <= in |""".stripMargin) result should containTree { case Port(_, "out", Output, AsyncResetType) => true } } it should "default to BoolType for Resets that are only invalidated" in { val result = compile(s""" |circuit test : | module test : | output out : Reset | out is invalid |""".stripMargin) result should containTree { case Port(_, "out", Output, BoolType) => true } } it should "not error if component of ResetType is invalidated and connected to an AsyncResetType" in { val result = compile(s""" |circuit test : | module test : | input cond : UInt<1> | input in : AsyncReset | output out : Reset | out is invalid | when cond : | out <= in |""".stripMargin) result should containTree { case Port(_, "out", Output, AsyncResetType) => true } } it should "allow ResetType to drive AsyncResets or UInt<1>" in { val result1 = compile(s""" |circuit top : | module top : | input in : UInt<1> | output out : UInt<1> | wire w : Reset | w <= in | out <= w |""".stripMargin) result1 should containTree { case DefWire(_, "w", BoolType) => true } val result2 = compile(s""" |circuit top : | module top : | output foo : { flip a : UInt<1> } | input bar : { flip a : UInt<1> } | wire w : { flip a : Reset } | foo <= w | w <= bar |""".stripMargin) val AggType = BundleType(Seq(Field("a", Flip, BoolType))) result2 should containTree { case DefWire(_, "w", AggType) => true } val result3 = compile(s""" |circuit top : | module top : | input in : UInt<1> | output out : UInt<1> | wire w : Reset | w <- in | out <- w |""".stripMargin) result3 should containTree { case DefWire(_, "w", BoolType) => true } } it should "error if a ResetType driving UInt<1> infers to AsyncReset" in { an[Exception] shouldBe thrownBy { compile(s""" |circuit top : | module top : | input in : AsyncReset | output out : UInt<1> | wire w : Reset | w <= in | out <= w |""".stripMargin) } } it should "error if a ResetType driving AsyncReset infers to UInt<1>" in { an[Exception] shouldBe thrownBy { compile(s""" |circuit top : | module top : | input in : UInt<1> | output out : AsyncReset | wire w : Reset | w <= in | out <= w |""".stripMargin) } } it should "not allow ResetType as an Input or ExtModule output" in { // TODO what exception should be thrown here? an[CheckHighForm.ResetInputException] shouldBe thrownBy { val result = compile(s""" |circuit top : | module top : | input in : { foo : Reset } | output out : Reset | out <= in.foo |""".stripMargin) } an[CheckHighForm.ResetExtModuleOutputException] shouldBe thrownBy { val result = compile(s""" |circuit top : | extmodule ext : | output out : { foo : Reset } | module top : | output out : Reset | inst e of ext | out <= e.out.foo |""".stripMargin) } } it should "not allow Vecs to infer different Reset Types" in { an[CheckTypes.InvalidConnect] shouldBe thrownBy { val result = compile(s""" |circuit top : | module top : | input reset0 : AsyncReset | input reset1 : UInt<1> | output out : Reset[2] | out[0] <= reset0 | out[1] <= reset1 |""".stripMargin) } } // Or is this actually an error? The behavior is that out is inferred as AsyncReset[2] ignore should "not allow Vecs only be partially inferred" in { // Some exception should be thrown, TODO figure out which one an[Exception] shouldBe thrownBy { val result = compile(s""" |circuit top : | module top : | input reset : AsyncReset | output out : Reset[2] | out is invalid | out[0] <= reset |""".stripMargin) } } it should "support inferring modules that would dedup differently" in { val result = compile(s""" |circuit top : | module child : | input clock : Clock | input childReset : Reset | input x : UInt<8> | output z : UInt<8> | reg r : UInt<8>, clock with : (reset => (childReset, UInt(123))) | r <= x | z <= r | module child_1 : | input clock : Clock | input childReset : Reset | input x : UInt<8> | output z : UInt<8> | reg r : UInt<8>, clock with : (reset => (childReset, UInt(123))) | r <= x | z <= r | module top : | input clock : Clock | input reset1 : UInt<1> | input reset2 : AsyncReset | input x : UInt<8>[2] | output z : UInt<8>[2] | inst c of child | c.clock <= clock | c.childReset <= reset1 | c.x <= x[0] | z[0] <= c.z | inst c2 of child_1 | c2.clock <= clock | c2.childReset <= reset2 | c2.x <= x[1] | z[1] <= c2.z |""".stripMargin) result should containTree { case Port(_, "childReset", Input, BoolType) => true } result should containTree { case Port(_, "childReset", Input, AsyncResetType) => true } } it should "infer based on what a component *drives* not just what drives it" in { val result = compile(s""" |circuit top : | module top : | input in : AsyncReset | output out : Reset | wire w : Reset | w is invalid | out <= w | out <= in |""".stripMargin) result should containTree { case DefWire(_, "w", AsyncResetType) => true } } it should "infer from connections, ignoring the fact that the invalidation wins" in { val result = compile(s""" |circuit top : | module top : | input in : AsyncReset | output out : Reset | out <= in | out is invalid |""".stripMargin) result should containTree { case Port(_, "out", Output, AsyncResetType) => true } } // The backwards type propagation constrains `w` to be the same as both `out0` and `out1` it should "not allow an invalidated Wire to drive both a UInt<1> and an AsyncReset" in { an[InferResets.InferResetsException] shouldBe thrownBy { val result = compile(s""" |circuit top : | module top : | input in0 : AsyncReset | input in1 : UInt<1> | output out0 : Reset | output out1 : Reset | wire w : Reset | w is invalid | out0 <= w | out1 <= w | out0 <= in0 | out1 <= in1 |""".stripMargin) } } it should "not propagate type info from downstream across a cast" in { val result = compile(s""" |circuit top : | module top : | input in0 : AsyncReset | input in1 : UInt<1> | output out0 : Reset | output out1 : Reset | wire w : Reset | w is invalid | out0 <= asAsyncReset(w) | out1 <= w | out0 <= in0 | out1 <= in1 |""".stripMargin) result should containTree { case Port(_, "out0", Output, AsyncResetType) => true } } // This tests for a bug unrelated to support or lackthereof for last connect in inference it should "take into account both internal and external constraints on Module port types" in { val result = compile(s""" |circuit top : | module child : | input i : AsyncReset | output o : Reset | o <= i | module top : | input in : AsyncReset | output out : AsyncReset | inst c of child | c.o is invalid | c.i <= in | out <= c.o |""".stripMargin) result should containTree { case Port(_, "o", Output, AsyncResetType) => true } } it should "not crash on combinational loops" in { a[CheckCombLoops.CombLoopException] shouldBe thrownBy { val result = compile( s""" |circuit top : | module top : | input in : AsyncReset | output out : Reset | wire w0 : Reset | wire w1 : Reset | w0 <= in | w0 <= w1 | w1 <= w0 | out <= in |""".stripMargin, compiler = new LowFirrtlCompiler ) } } }