// SPDX-License-Identifier: Apache-2.0

package firrtlTests

import firrtl.CircuitState
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import firrtl.ir.Circuit
import firrtl.options.Dependency
import firrtl.testutils.LeanTransformSpec
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

/**
  * An example methodology for testing Firrtl compilers.
  *
  * Given an input Firrtl circuit (expressed as a string),
  * the compiler is executed. The output of the compiler
  * should be compared against the check string.
  */
abstract class CompilerSpec(emitter: Dependency[firrtl.Emitter]) extends LeanTransformSpec(Seq(emitter)) {
  def input: String
  def getOutput: String = compile(input).getEmittedCircuit.value
}

/**
  * An example test for testing the HighFirrtlCompiler.
  *
  * Given an input Firrtl circuit (expressed as a string),
  * the compiler is executed. The output of the compiler
  * is parsed again and compared (in-memory) to the parsed
  * input.
  */
class HighFirrtlCompilerSpec extends CompilerSpec(Dependency[firrtl.HighFirrtlEmitter]) with Matchers {
  val input =
    """circuit Top :
  module Top :
    input a : UInt<1>[2]
    node x = a
"""
  val check = input
  "Any circuit" should "match exactly to its input" in {
    (parse(getOutput)) should be(parse(check))
  }
}

/**
  * An example test for testing the MiddleFirrtlCompiler.
  *
  * Given an input Firrtl circuit (expressed as a string),
  * the compiler is executed. The output of the compiler is
  * a lowered (to MidForm) version of the input circuit. The output is
  * string compared to the correct lowered circuit.
  */
class MiddleFirrtlCompilerSpec extends CompilerSpec(Dependency[firrtl.MiddleFirrtlEmitter]) with Matchers {
  val input =
    """
circuit Top :
  module Top :
    input reset : UInt<1>
    input a : UInt<1>[2]
    wire b : UInt
    b <= a[0]
    when reset :
      b <= UInt(0)
"""
  // Verify that Vecs are retained, but widths are inferred and whens are expanded.
  val check = Seq(
    "circuit Top :",
    "  module Top :",
    "    input reset : UInt<1>",
    "    input a : UInt<1>[2]",
    "    wire b : UInt<1>",
    "    node _GEN_0 = mux(reset, UInt<1>(\"h0\"), a[0])",
    "    b <= _GEN_0\n\n"
  ).reduce(_ + "\n" + _)
  "A circuit" should "match exactly to its MidForm state" in {
    (parse(getOutput)) should be(parse(check))
  }
}

/**
  * An example test for testing the LoweringCompiler.
  *
  * Given an input Firrtl circuit (expressed as a string),
  * the compiler is executed. The output of the compiler is
  * a lowered version of the input circuit. The output is
  * string compared to the correct lowered circuit.
  */
class LowFirrtlCompilerSpec extends CompilerSpec(Dependency[firrtl.LowFirrtlEmitter]) with Matchers {
  val input =
    """
circuit Top :
  module Top :
    input a : UInt<1>[2]
    node x = a
"""
  val check = Seq(
    "circuit Top :",
    "  module Top :",
    "    input a_0 : UInt<1>",
    "    input a_1 : UInt<1>",
    "    node x_0 = a_0",
    "    node x_1 = a_1\n\n"
  ).reduce(_ + "\n" + _)
  "A circuit" should "match exactly to its lowered state" in {
    (parse(getOutput)) should be(parse(check))
  }
}

/**
  * An example test for testing the VerilogCompiler.
  *
  * Given an input Firrtl circuit (expressed as a string),
  * the compiler is executed. The output of the compiler is
  * the corresponding Verilog. The output is string compared
  * to the correct Verilog.
  */
class VerilogCompilerSpec extends CompilerSpec(Dependency[firrtl.VerilogEmitter]) with Matchers {
  val input = """circuit Top :
                |  module Top :
                |    input a : UInt<1>[2]
                |    output b : UInt<1>[2]
                |    b <= a""".stripMargin
  val check = """module Top(
                |  input   a_0,
                |  input   a_1,
                |  output  b_0,
                |  output  b_1
                |);
                |  assign b_0 = a_0;
                |  assign b_1 = a_1;
                |endmodule
                |""".stripMargin
  "A circuit's verilog output" should "match the given string and not have RANDOMIZE if no invalids" in {
    getOutput should be(check)
  }
}

class MinimumVerilogCompilerSpec extends CompilerSpec(Dependency[firrtl.MinimumVerilogEmitter]) with Matchers {
  val input = """|circuit Top:
                 |  module Top:
                 |    output b: UInt<1>[3]
                 |    input i: SInt<3>
                 |    output o: SInt<5>
                 |    node c = bits(UInt<3>("h7"), 2, 2)
                 |    node d = shr(UInt<3>("h7"), 2)
                 |    b[0] is invalid
                 |    b[1] <= c
                 |    b[2] <= d
                 |    o <= i
                 |""".stripMargin
  val check = """|module Top(
                 |  output       b_0,
                 |  output       b_1,
                 |  output       b_2,
                 |  input  [2:0] i,
                 |  output [4:0] o
                 |);
                 |  wire  c = 1'h1;
                 |  wire  d = 1'h1;
                 |  assign b_0 = 1'h0;
                 |  assign b_1 = c;
                 |  assign b_2 = d;
                 |  assign o = {{2{i[2]}},i};
                 |endmodule
                 |""".stripMargin
  "A circuit's minimum Verilog output" should "pad signed RHSes but not reflect any const-prop or DCE" in {
    getOutput should be(check)
  }
}
