// SPDX-License-Identifier: Apache-2.0

package firrtlTests.transforms

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

class LegalizeClocksTransformSpec extends FirrtlFlatSpec {
  def compile(input: String): CircuitState =
    (new MinimumVerilogCompiler).compileAndEmit(CircuitState(parse(input), ChirrtlForm), Nil)

  behavior.of("LegalizeClocksTransform")

  it should "not emit @(posedge 1'h0) for stop" in {
    val input =
      """circuit test :
        |  module test :
        |    stop(asClock(UInt(1)), UInt(1), 1)
        |""".stripMargin
    val result = compile(input)
    result should containLine(s"always @(posedge _GEN_0) begin")
    result.getEmittedCircuit.value shouldNot include("always @(posedge 1")
  }

  it should "not emit @(posedge 1'h0) for printf" in {
    val input =
      """circuit test :
        |  module test :
        |    printf(asClock(UInt(1)), UInt(1), "hi")
        |""".stripMargin
    val result = compile(input)
    result should containLine(s"always @(posedge _GEN_0) begin")
    result.getEmittedCircuit.value shouldNot include("always @(posedge 1")
  }

  it should "not emit @(posedge 1'h0) for reg" in {
    val input =
      """circuit test :
        |  module test :
        |    output out : UInt<8>
        |    input in : UInt<8>
        |    reg r : UInt<8>, asClock(UInt(0))
        |    r <= in
        |    out <= r
        |""".stripMargin
    val result = compile(input)
    result should containLine(s"always @(posedge _GEN_0) begin")
    result.getEmittedCircuit.value shouldNot include("always @(posedge 1")
  }

  it should "not emit @(posedge 1'h0) for mem" in {
    val input =
      """circuit test :
        |  module test :
        |    output rdata : UInt<8>
        |    input wdata : UInt<8>
        |    input addr : UInt<5>
        |    mem m :
        |      data-type => UInt<8>
        |      depth => 32
        |      read-latency => 0
        |      write-latency => 1
        |      reader => r
        |      writer => w
        |      read-under-write => undefined
        |    m.r.clk <= asClock(UInt(0))
        |    m.r.en <= UInt(1)
        |    m.r.addr <= addr
        |    rdata <= m.r.data
        |    m.w.clk <= asClock(UInt(0))
        |    m.w.en <= UInt(1)
        |    m.w.mask <= UInt(1)
        |    m.w.addr <= addr
        |    m.w.data <= wdata
        |""".stripMargin
    val result = compile(input)
    result should containLine(s"always @(posedge _GEN_1) begin")
    result.getEmittedCircuit.value shouldNot include("always @(posedge 1")
  }

  it should "not emit @(posedge clock or posedge 1'h0) for a constantly deasserted areset" in {
    val input = """circuit test :
                  |  module test :
                  |    input clock : Clock
                  |    input i: UInt<1>
                  |    output z: UInt<1>
                  |    wire reset : AsyncReset
                  |    reset <= asAsyncReset(UInt<1>("h0"))
                  |    reg r : UInt<1>, clock with : (reset => (reset, UInt(0)))
                  |    r <= i
                  |    z <= r""".stripMargin
    val result = compile(input)
    result should containLine(s"always @(posedge clock or posedge _GEN_0) begin")
    result.getEmittedCircuit.value shouldNot include("always @(posedge clock or posedge 1")
  }

  it should "not emit @(posedge clock or posedge 1'h1) for a constantly asserted areset" in {
    val input = """circuit test :
                  |  module test :
                  |    input clock : Clock
                  |    input i: UInt<1>
                  |    output z: UInt<1>
                  |    wire reset : AsyncReset
                  |    reset <= asAsyncReset(UInt<1>("h1"))
                  |    reg r : UInt<1>, clock with : (reset => (reset, UInt(0)))
                  |    r <= i
                  |    z <= r""".stripMargin
    val result = compile(input)
    result should containLine(s"always @(posedge clock or posedge _GEN_0) begin")
    result.getEmittedCircuit.value shouldNot include("always @(posedge clock or posedge 1")
  }

  it should "not emit @(posedge 1'h0 or posedge 1'h0) for a reg with tied off clocks and areset" in {
    val input = """circuit test :
                  |  module test :
                  |    input i: UInt<1>
                  |    output z: UInt<1>
                  |    wire clock : Clock
                  |    clock <= asClock(UInt(1))
                  |    wire reset : AsyncReset
                  |    reset <= asAsyncReset(UInt<1>("h0"))
                  |    reg r : UInt<1>, clock with : (reset => (reset, UInt(0)))
                  |    r <= i
                  |    z <= r""".stripMargin
    val result = compile(input)
    result should containLine(s"always @(posedge _GEN_0 or posedge _GEN_1) begin")
    result.getEmittedCircuit.value shouldNot include("always @(posedge clock or posedge 1")
    result.getEmittedCircuit.value shouldNot include("or posedge 1")
  }

  it should "deduplicate injected nodes for literal clocks" in {
    val input =
      """circuit test :
        |  module test :
        |    printf(asClock(UInt(1)), UInt(1), "hi")
        |    stop(asClock(UInt(1)), UInt(1), 1)
        |""".stripMargin
    val result = compile(input)
    result should containLine(s"wire  _GEN_0 = 1'h1;")
    // Check that there's only 1 _GEN_0 instantiation
    val verilog = result.getEmittedCircuit.value
    val matches = "wire\\s+_GEN_0\\s+=\\s+1'h1".r.findAllIn(verilog)
    matches.size should be(1)

  }
}
