// SPDX-License-Identifier: Apache-2.0

package firrtlTests.formal

import firrtl.{CircuitState, Parser, Transform, UnknownForm}
import firrtl.testutils.FirrtlFlatSpec
import firrtl.transforms.formal.AssertSubmoduleAssumptions
import firrtl.stage.{Forms, TransformManager}

class AssertSubmoduleAssumptionsSpec extends FirrtlFlatSpec {
  behavior.of("AssertSubmoduleAssumptions")

  val transforms = new TransformManager(Forms.HighForm, Forms.MinimalHighForm).flattenedTransformOrder ++ Seq(
    new AssertSubmoduleAssumptions
  )

  def run(input: String, check: Seq[String], debug: Boolean = false): Unit = {
    val circuit = Parser.parse(input.split("\n").toIterator)
    val result = transforms.foldLeft(CircuitState(circuit, UnknownForm)) { (c: CircuitState, p: Transform) =>
      p.runTransform(c)
    }
    val lines = result.circuit.serialize.split("\n").map(normalized)

    if (debug) {
      println(lines.mkString("\n"))
    }

    for (ch <- check) {
      lines should contain(ch)
    }
  }

  it should "convert `assume` to `assert` in a submodule" in {
    val input =
      """circuit Test :
        |  module Test :
        |    input clock : Clock
        |    input reset : UInt<1>
        |    input in : UInt<8>
        |    output out : UInt<8>
        |    inst sub of Sub
        |    sub.clock <= clock
        |    sub.reset <= reset
        |    sub.in <= in
        |    out <= sub.out
        |    assume(clock, eq(in, UInt(0)), UInt(1), "assume0")
        |    assert(clock, eq(out, UInt(0)), UInt(1), "assert0")
        |
        |  module Sub :
        |    input clock : Clock
        |    input reset : UInt<1>
        |    input in : UInt<8>
        |    output out : UInt<8>
        |    out <= in
        |    assume(clock, eq(in, UInt(1)), UInt(1), "assume1")
        |    assert(clock, eq(out, UInt(1)), UInt(1), "assert1")
        |""".stripMargin

    val check = Seq(
      "assert(clock, eq(in, UInt<1>(\"h1\")), UInt<1>(\"h1\"), \"assume1\")"
    )
    run(input, check)
  }

  it should "convert `assume` to `assert` in a nested submodule" in {
    val input =
      """circuit Test :
        |  module Test :
        |    input clock : Clock
        |    input reset : UInt<1>
        |    input in : UInt<8>
        |    output out : UInt<8>
        |    inst sub of Sub
        |    sub.clock <= clock
        |    sub.reset <= reset
        |    sub.in <= in
        |    out <= sub.out
        |    assume(clock, eq(in, UInt(0)), UInt(1), "assume0")
        |    assert(clock, eq(out, UInt(0)), UInt(1), "assert0")
        |
        |  module Sub :
        |    input clock : Clock
        |    input reset : UInt<1>
        |    input in : UInt<8>
        |    output out : UInt<8>
        |    inst nestedSub of NestedSub
        |    nestedSub.clock <= clock
        |    nestedSub.reset <= reset
        |    nestedSub.in <= in
        |    out <= nestedSub.out
        |    assume(clock, eq(in, UInt(1)), UInt(1), "assume1")
        |    assert(clock, eq(out, UInt(1)), UInt(1), "assert1")
        |
        |  module NestedSub :
        |    input clock : Clock
        |    input reset : UInt<1>
        |    input in : UInt<8>
        |    output out : UInt<8>
        |    out <= in
        |    assume(clock, eq(in, UInt(2)), UInt(1), "assume2")
        |    assert(clock, eq(out, UInt(2)), UInt(1), "assert2")
        |""".stripMargin

    val check = Seq(
      "assert(clock, eq(in, UInt<1>(\"h1\")), UInt<1>(\"h1\"), \"assume1\")",
      "assert(clock, eq(in, UInt<2>(\"h2\")), UInt<1>(\"h1\"), \"assume2\")"
    )
    run(input, check)
  }
}
