// SPDX-License-Identifier: Apache-2.0

package firrtlTests

import firrtl._
import firrtl.testutils._
import FirrtlCheckers._

class AsyncResetSpec extends VerilogTransformSpec {
  def compileBody(body: String) = {
    val str = """
                |circuit Test :
                |  module Test :
                |""".stripMargin + body.split("\n").mkString("    ", "\n    ", "")
    compile(str)
  }

  "AsyncReset" should "generate async-reset always blocks" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input reset : AsyncReset
                                |input x : UInt<8>
                                |output z : UInt<8>
                                |reg r : UInt<8>, clock with : (reset => (reset, UInt(123)))
                                |r <= x
                                |z <= r""".stripMargin)
    result should containLine("always @(posedge clock or posedge reset) begin")
  }

  it should "work in nested and flipped aggregates with regular and partial connect" in {
    val result = compileBody(s"""
                                |output fizz : { flip foo : { a : AsyncReset, flip b: AsyncReset }[2], bar : { a : AsyncReset, flip b: AsyncReset }[2] }
                                |output buzz : { flip foo : { a : AsyncReset, flip b: AsyncReset }[2], bar : { a : AsyncReset, flip b: AsyncReset }[2] }
                                |fizz.bar <= fizz.foo
                                |buzz.bar <- buzz.foo
                                |""".stripMargin)

    result should containLine("assign fizz_foo_0_b = fizz_bar_0_b;")
    result should containLine("assign fizz_foo_1_b = fizz_bar_1_b;")
    result should containLine("assign fizz_bar_0_a = fizz_foo_0_a;")
    result should containLine("assign fizz_bar_1_a = fizz_foo_1_a;")
    result should containLine("assign buzz_foo_0_b = buzz_bar_0_b;")
    result should containLine("assign buzz_foo_1_b = buzz_bar_1_b;")
    result should containLine("assign buzz_bar_0_a = buzz_foo_0_a;")
    result should containLine("assign buzz_bar_1_a = buzz_foo_1_a;")
  }

  it should "support casting to other types" in {
    val result = compileBody(s"""
                                |input a : AsyncReset
                                |output u : Interval[0, 1].0
                                |output v : UInt<1>
                                |output w : SInt<1>
                                |output x : Clock
                                |output y : Fixed<1><<0>>
                                |output z : AsyncReset
                                |u <= asInterval(a, 0, 1, 0)
                                |v <= asUInt(a)
                                |w <= asSInt(a)
                                |x <= asClock(a)
                                |y <= asFixedPoint(a, 0)
                                |z <= asAsyncReset(a)
                                |""".stripMargin)
    result should containLine("assign v = a;")
    result should containLine("assign w = a;")
    result should containLine("assign x = a;")
    result should containLine("assign y = a;")
    result should containLine("assign z = a;")
  }

  "Other types" should "support casting to AsyncReset" in {
    val result = compileBody(s"""
                                |input a : UInt<1>
                                |input b : SInt<1>
                                |input c : Clock
                                |input d : Fixed<1><<0>>
                                |input e : AsyncReset
                                |input f : Interval[0, 0].0
                                |output u : AsyncReset
                                |output v : AsyncReset
                                |output w : AsyncReset
                                |output x : AsyncReset
                                |output y : AsyncReset
                                |output z : AsyncReset
                                |u <= asAsyncReset(a)
                                |v <= asAsyncReset(b)
                                |w <= asAsyncReset(c)
                                |x <= asAsyncReset(d)
                                |y <= asAsyncReset(e)
                                |z <= asAsyncReset(f)""".stripMargin)
    result should containLine("assign u = a;")
    result should containLine("assign v = b;")
    result should containLine("assign w = c;")
    result should containLine("assign x = d;")
    result should containLine("assign y = e;")
    result should containLine("assign z = f;")
  }

  "Non-literals" should "NOT be allowed as reset values for AsyncReset" in {
    an[checks.CheckResets.NonLiteralAsyncResetValueException] shouldBe thrownBy {
      compileBody(s"""
                     |input clock : Clock
                     |input reset : AsyncReset
                     |input x : UInt<8>
                     |input y : UInt<8>
                     |output z : UInt<8>
                     |reg r : UInt<8>, clock with : (reset => (reset, y))
                     |r <= x
                     |z <= r""".stripMargin)
    }
  }

  "Self-inits" should "NOT cause infinite loops in CheckResets" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input reset : AsyncReset
                                |input in : UInt<12>
                                |output out : UInt<10>
                                |
                                |reg a : UInt<10>, clock with :
                                |  reset => (reset, a)
                                |out <= UInt<5>("h15")""".stripMargin)
    result should containLine("assign out = 10'h15;")
  }

  "Late non-literals connections" should "NOT be allowed as reset values for AsyncReset" in {
    an[checks.CheckResets.NonLiteralAsyncResetValueException] shouldBe thrownBy {
      compileBody(s"""
                     |input clock : Clock
                     |input reset : AsyncReset
                     |input x : UInt<8>
                     |input y : UInt<8>
                     |output z : UInt<8>
                     |wire a : UInt<8>
                     |reg r : UInt<8>, clock with : (reset => (reset, a))
                     |a <= y
                     |r <= x
                     |z <= r""".stripMargin)
    }
  }

  "Hidden Non-literals" should "NOT be allowed as reset values for AsyncReset" in {
    an[checks.CheckResets.NonLiteralAsyncResetValueException] shouldBe thrownBy {
      compileBody(s"""
                     |input clock : Clock
                     |input reset : AsyncReset
                     |input x : UInt<1>[4]
                     |input y : UInt<1>
                     |output z : UInt<1>[4]
                     |wire literal : UInt<1>[4]
                     |literal[0] <= UInt<1>("h00")
                     |literal[1] <= y
                     |literal[2] <= UInt<1>("h00")
                     |literal[3] <= UInt<1>("h00")
                     |reg r : UInt<1>[4], clock with : (reset => (reset, literal))
                     |r <= x
                     |z <= r""".stripMargin)
    }
  }
  "Wire connected to non-literal" should "NOT be allowed as reset values for AsyncReset" in {
    an[checks.CheckResets.NonLiteralAsyncResetValueException] shouldBe thrownBy {
      compileBody(s"""
                     |input clock : Clock
                     |input reset : AsyncReset
                     |input x : UInt<1>
                     |input y : UInt<1>
                     |input cond : UInt<1>
                     |output z : UInt<1>
                     |wire w : UInt<1>
                     |w <= UInt(1)
                     |when cond :
                     |  w <= y
                     |reg r : UInt<1>, clock with : (reset => (reset, w))
                     |r <= x
                     |z <= r""".stripMargin)
    }
  }

  "Complex literals" should "be allowed as reset values for AsyncReset" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input reset : AsyncReset
                                |input x : UInt<1>[4]
                                |output z : UInt<1>[4]
                                |wire literal : UInt<1>[4]
                                |literal[0] <= UInt<1>("h00")
                                |literal[1] <= UInt<1>("h00")
                                |literal[2] <= UInt<1>("h00")
                                |literal[3] <= UInt<1>("h00")
                                |reg r : UInt<1>[4], clock with : (reset => (reset, literal))
                                |r <= x
                                |z <= r""".stripMargin)
    result should containLine("always @(posedge clock or posedge reset) begin")
  }

  "Complex literals of complex literals" should "be allowed as reset values for AsyncReset" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input reset : AsyncReset
                                |input x : UInt<1>[4]
                                |output z : UInt<1>[4]
                                |wire literal : UInt<1>[2]
                                |literal[0] <= UInt<1>("h01")
                                |literal[1] <= UInt<1>("h01")
                                |wire complex_literal : UInt<1>[4]
                                |complex_literal[0] <= literal[0]
                                |complex_literal[1] <= literal[1]
                                |complex_literal[2] <= UInt<1>("h00")
                                |complex_literal[3] <= UInt<1>("h00")
                                |reg r : UInt<1>[4], clock with : (reset => (reset, complex_literal))
                                |r <= x
                                |z <= r""".stripMargin)
    result should containLine("always @(posedge clock or posedge reset) begin")
  }
  "Literals of bundle literals" should "be allowed as reset values for AsyncReset" in {
    val result = compileBody(s"""
                                |input clock : Clock
                                |input reset : AsyncReset
                                |input x : UInt<1>[4]
                                |output z : UInt<1>[4]
                                |wire bundle : {a: UInt<1>, b: UInt<1>}
                                |bundle.a <= UInt<1>("h01")
                                |bundle.b <= UInt<1>("h01")
                                |wire complex_literal : UInt<1>[4]
                                |complex_literal[0] <= bundle.a
                                |complex_literal[1] <= bundle.b
                                |complex_literal[2] <= UInt<1>("h00")
                                |complex_literal[3] <= UInt<1>("h00")
                                |reg r : UInt<1>[4], clock with : (reset => (reset, complex_literal))
                                |r <= x
                                |z <= r""".stripMargin)
    result should containLine("always @(posedge clock or posedge reset) begin")
  }

  "Cast literals" should "be allowed as reset values for AsyncReset" in {
    // This also checks that casts can be across wires and nodes
    val sintResult = compileBody(s"""
                                    |input clock : Clock
                                    |input reset : AsyncReset
                                    |input x : SInt<4>
                                    |output y : SInt<4>
                                    |output z : SInt<4>
                                    |reg r : SInt<4>, clock with : (reset => (reset, asSInt(UInt(0))))
                                    |r <= x
                                    |wire w : SInt<4>
                                    |reg r2 : SInt<4>, clock with : (reset => (reset, w))
                                    |r2 <= x
                                    |node n = UInt("hf")
                                    |w <= asSInt(n)
                                    |y <= r2
                                    |z <= r""".stripMargin)
    sintResult should containLine("always @(posedge clock or posedge reset) begin")
    sintResult should containLine("r <= 4'sh0;")
    sintResult should containLine("r2 <= -4'sh1;")

    val fixedResult = compileBody(s"""
                                     |input clock : Clock
                                     |input reset : AsyncReset
                                     |input x : Fixed<2><<0>>
                                     |output z : Fixed<2><<0>>
                                     |reg r : Fixed<2><<0>>, clock with : (reset => (reset, asFixedPoint(UInt(2), 0)))
                                     |r <= x
                                     |z <= r""".stripMargin)
    fixedResult should containLine("always @(posedge clock or posedge reset) begin")
    fixedResult should containLine("r <= 2'sh2;")

    val intervalResult = compileBody(s"""
                                        |input clock : Clock
                                        |input reset : AsyncReset
                                        |input x : Interval[0, 4].0
                                        |output z : Interval[0, 4].0
                                        |reg r : Interval[0, 4].0, clock with : (reset => (reset, asInterval(UInt(0), 0, 0, 0)))
                                        |r <= x
                                        |z <= r""".stripMargin)
    intervalResult should containLine("always @(posedge clock or posedge reset) begin")
    intervalResult should containLine("r <= 4'sh0;")
  }

  "CheckResets" should "NOT raise StackOverflow Exception on Combinational Loops (should be caught by firrtl.transforms.CheckCombLoops)" in {
    an[firrtl.transforms.CheckCombLoops.CombLoopException] shouldBe thrownBy {
      compileBody(s"""
                     |input clock : Clock
                     |input reset : AsyncReset
                     |wire x : UInt<1>
                     |wire y : UInt<2>
                     |x <= UInt<1>("h01")
                     |node ad = add(x, y)
                     |node adt = tail(ad, 1)
                     |y <= adt
                     |reg r : UInt, clock with : (reset => (reset, y))
                     |""".stripMargin)
    }
  }

  "Every async reset reg" should "generate its own always block" in {
    val result = compileBody(s"""
                                |input clock0 : Clock
                                |input clock1 : Clock
                                |input syncReset : UInt<1>
                                |input asyncReset : AsyncReset
                                |input x : UInt<8>[5]
                                |output z : UInt<8>[5]
                                |reg r0 : UInt<8>, clock0 with : (reset => (syncReset, UInt(123)))
                                |reg r1 : UInt<8>, clock1 with : (reset => (syncReset, UInt(123)))
                                |reg r2 : UInt<8>, clock0 with : (reset => (asyncReset, UInt(123)))
                                |reg r3 : UInt<8>, clock0 with : (reset => (asyncReset, UInt(123)))
                                |reg r4 : UInt<8>, clock1 with : (reset => (asyncReset, UInt(123)))
                                |r0 <= x[0]
                                |r1 <= x[1]
                                |r2 <= x[2]
                                |r3 <= x[3]
                                |r4 <= x[4]
                                |z[0] <= r0
                                |z[1] <= r1
                                |z[2] <= r2
                                |z[3] <= r3
                                |z[4] <= r4""".stripMargin)
    result should containLines(
      "always @(posedge clock0) begin",
      "if (syncReset) begin",
      "r0 <= 8'h7b;",
      "end else begin",
      "r0 <= x_0;",
      "end",
      "end"
    )
    result should containLines(
      "always @(posedge clock1) begin",
      "if (syncReset) begin",
      "r1 <= 8'h7b;",
      "end else begin",
      "r1 <= x_1;",
      "end",
      "end"
    )
    result should containLines(
      "always @(posedge clock0 or posedge asyncReset) begin",
      "if (asyncReset) begin",
      "r2 <= 8'h7b;",
      "end else begin",
      "r2 <= x_2;",
      "end",
      "end"
    )
    result should containLines(
      "always @(posedge clock0 or posedge asyncReset) begin",
      "if (asyncReset) begin",
      "r3 <= 8'h7b;",
      "end else begin",
      "r3 <= x_3;",
      "end",
      "end"
    )
    result should containLines(
      "always @(posedge clock1 or posedge asyncReset) begin",
      "if (asyncReset) begin",
      "r4 <= 8'h7b;",
      "end else begin",
      "r4 <= x_4;",
      "end",
      "end"
    )
  }

  "Unassigned asyncronously reset registers" should "properly constantprop" in {
    val result = compileBody(
      s"""
         |input clock : Clock
         |input reset : AsyncReset
         |output z : UInt<1>[4]
         |wire literal : UInt<1>[2]
         |literal[0] <= UInt<1>("h01")
         |literal[1] <= UInt<1>("h01")
         |wire complex_literal : UInt<1>[4]
         |complex_literal[0] <= literal[0]
         |complex_literal[1] <= literal[1]
         |complex_literal[2] <= UInt<1>("h00")
         |complex_literal[3] <= UInt<1>("h00")
         |reg r : UInt<1>[4], clock with : (reset => (reset, complex_literal))
         |z <= r""".stripMargin
    )
    result shouldNot containLine("always @(posedge clock or posedge reset) begin")
  }

  "Constantly assigned asynchronously reset registers" should "properly constantprop" in {
    val result = compileBody(
      s"""
         |input clock : Clock
         |input reset : AsyncReset
         |output z : UInt<1>
         |reg r : UInt<1>, clock with : (reset => (reset, r))
         |r <= UInt(0)
         |z <= r""".stripMargin
    )
    result shouldNot containLine("always @(posedge clock or posedge reset) begin")
  }

  "Constantly assigned and initialized asynchronously reset registers" should "properly constantprop" in {
    val result = compileBody(
      s"""
         |input clock : Clock
         |input reset : AsyncReset
         |output z : UInt<1>
         |reg r : UInt<1>, clock with : (reset => (reset, UInt(0)))
         |r <= UInt(0)
         |z <= r""".stripMargin
    )
    result shouldNot containLine("always @(posedge clock or posedge reset) begin")
  }

  "AsyncReset registers" should "emit 'else' case for reset even for trivial valued registers" in {
    val withDontTouch = s"""
                           |circuit m :
                           |  module m :
                           |    input clock : Clock
                           |    input reset : AsyncReset
                           |    input x : UInt<8>
                           |    reg r : UInt<8>, clock with : (reset => (reset, UInt(123)))
                           |""".stripMargin
    val annos = Seq(dontTouch("m.r")) // dontTouch prevents ConstantPropagation from fixing this problem
    val result = (new VerilogCompiler).compileAndEmit(CircuitState(parse(withDontTouch), ChirrtlForm, annos))
    result should containLines(
      "always @(posedge clock or posedge reset) begin",
      "if (reset) begin",
      "r <= 8'h7b;",
      "end else begin",
      "r <= 8'h7b;",
      "end",
      "end"
    )
  }
}

class AsyncResetExecutionTest extends ExecutionTest("AsyncResetTester", "/features")
