// SPDX-License-Identifier: Apache-2.0

package firrtlTests

import firrtl._
import firrtl.ir.Circuit
import firrtl.passes._
import firrtl.testutils._

class InoutVerilogSpec extends FirrtlFlatSpec {

  behavior.of("Analog")

  it should "attach a module input source directly" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    input an: Analog<3>
        |    inst a of A
        |    inst b of B
        |    attach (an, a.an1, b.an2)
        |  module A:
        |    input an1: Analog<3>
        |  module B:
        |    input an2: Analog<3> """.stripMargin
    val check =
      """module Attaching(
        |  inout  [2:0] an
        |);
        |  A a (
        |    .an1(an)
        |  );
        |  B b (
        |    .an2(an)
        |  );
        |endmodule
        |module A(
        |  inout  [2:0] an1
        |);
        |endmodule
        |module B(
        |  inout  [2:0] an2
        |);
        |endmodule
        |""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler, Seq(dontDedup("A"), dontDedup("B")))
  }

  it should "attach two instances" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    inst a of A
        |    inst b of B
        |    attach (a.an, b.an)
        |  module A:
        |    input an: Analog<3>
        |  module B:
        |    input an: Analog<3>""".stripMargin
    val check =
      """module Attaching(
        |);
        |  wire [2:0] _GEN_0;
        |  A a (
        |    .an(_GEN_0)
        |  );
        |  B b (
        |    .an(_GEN_0)
        |  );
        |endmodule
        |module A(
        |  inout  [2:0] an
        |);
        |module B(
        |  inout  [2:0] an
        |);
        |endmodule
        |""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler, Seq(dontTouch("A.an"), dontDedup("A")))
  }

  it should "attach a wire source" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    wire x: Analog
        |    inst a of A
        |    attach (x, a.an)
        |  module A:
        |    input an: Analog<3> """.stripMargin
    val check =
      """module Attaching(
        |);
        |  wire [2:0] x;
        |  A a (
        |    .an(x)
        |  );
        |endmodule
        |""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler, Seq(dontTouch("Attaching.x")))
  }

  it should "attach port to submodule port through a wire" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    input an: Analog<3>
        |    wire x: Analog
        |    inst a of A
        |    attach (x, a.an)
        |    attach (x, an)
        |  module A:
        |    input an: Analog<3> """.stripMargin
    val check =
      """module Attaching(
        |  inout [2:0] an
        |);
        |  A a (
        |    .an(an)
        |  );
        |endmodule
        |""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler, Seq(dontTouch("Attaching.x")))
  }

  it should "attach multiple sources" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    input a1 : Analog<3>
        |    input a2 : Analog<3>
        |    wire x: Analog<3>
        |    attach (x, a1, a2)""".stripMargin
    val check =
      """module Attaching(
        |  inout  [2:0] a1,
        |  inout  [2:0] a2
        |);
        |  `ifdef SYNTHESIS
        |    assign a1 = a2;
        |    assign a2 = a1;
        |  `elsif verilator
        |    `error "Verilator does not support alias and thus cannot arbirarily connect bidirectional wires and ports"
        |  `else
        |    alias a1 = a2;
        |  `endif
        |endmodule
        |""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler)
  }

  it should "work in partial connect" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    input foo : { b : UInt<3>, a : Analog<3> }
        |    output bar : { b : UInt<3>, a : Analog<3> }
        |    bar <- foo""".stripMargin
    // Omitting `ifdef SYNTHESIS and `elsif verilator since it's tested above
    val check =
      """module Attaching(
        |  input  [2:0] foo_b,
        |  inout  [2:0] foo_a,
        |  output  [2:0] bar_b,
        |  inout  [2:0] bar_a
        |);
        |  assign bar_b = foo_b;
        |  alias bar_a = foo_a;
        |endmodule
        |""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler)
  }

  it should "preserve attach order" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    input a : Analog<32>
        |    input b : Analog<32>
        |    input c : Analog<32>
        |    input d : Analog<32>
        |    attach (a, b)
        |    attach (c, b)
        |    attach (a, d)""".stripMargin
    val check =
      """module Attaching(
        |  inout  [31:0] a,
        |  inout  [31:0] b,
        |  inout  [31:0] c,
        |  inout  [31:0] d
        |);
        |    alias a = b = c = d;
        |endmodule
        |""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler)

    val input2 =
      """circuit Attaching :
        |  module Attaching :
        |    input a : Analog<32>
        |    input b : Analog<32>
        |    input c : Analog<32>
        |    input d : Analog<32>
        |    attach (a, b)
        |    attach (c, d)
        |    attach (d, a)""".stripMargin
    val check2 =
      """module Attaching(
        |  inout  [31:0] a,
        |  inout  [31:0] b,
        |  inout  [31:0] c,
        |  inout  [31:0] d
        |);
        |    alias a = b = c = d;
        |endmodule
        |""".stripMargin.split("\n").map(normalized)
    executeTest(input2, check2, compiler)
  }

  it should "infer widths" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    input an: Analog
        |    inst a of A
        |    attach (an, a.an1)
        |  module A:
        |    input an1: Analog<3>""".stripMargin
    val check =
      """module Attaching(
        |  inout  [2:0] an
        |);
        |  A a (
        |    .an1(an)
        |  );
        |endmodule
        |module A(
        |  inout  [2:0] an1
        |);
        |endmodule""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler)
  }

  it should "not error if not isinvalid" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    output an: Analog<3>
        |""".stripMargin
    val check =
      """module Attaching(
        |  inout  [2:0] an
        |);
        |endmodule""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler)
  }
  it should "not error if isinvalid" in {
    val compiler = new VerilogCompiler
    val input =
      """circuit Attaching :
        |  module Attaching :
        |    output an: Analog<3>
        |    an is invalid
        |""".stripMargin
    val check =
      """module Attaching(
        |  inout  [2:0] an
        |);
        |endmodule""".stripMargin.split("\n").map(normalized)
    executeTest(input, check, compiler)
  }
}

class AttachAnalogSpec extends FirrtlFlatSpec {
  private def executeTest(input: String, expected: Seq[String], passes: Seq[Pass]) = {
    val c = passes.foldLeft(Parser.parse(input.split("\n").toIterator)) { (c: Circuit, p: Pass) =>
      p.run(c)
    }
    val lines = c.serialize.split("\n").map(normalized)

    expected.foreach { e =>
      lines should contain(e)
    }
  }

  "Connecting analog types" should "throw an exception" in {
    val passes = Seq(ToWorkingIR, CheckHighForm, ResolveKinds, InferTypes, CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input y: Analog<1>
        |    output x: Analog<1>
        |    x <= y""".stripMargin
    intercept[CheckTypes.InvalidConnect] {
      passes.foldLeft(parse(input)) { (c: Circuit, p: Pass) =>
        p.run(c)
      }
    }
  }

  "Declaring register with analog types" should "throw an exception" in {
    val passes = Seq(ToWorkingIR, CheckHighForm, ResolveKinds, InferTypes, CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input clk: Clock
        |    reg r: Analog<2>, clk""".stripMargin
    intercept[CheckTypes.IllegalAnalogDeclaration] {
      passes.foldLeft(parse(input)) { (c: Circuit, p: Pass) =>
        p.run(c)
      }
    }
  }

  "Declaring memory with analog types" should "throw an exception" in {
    val passes = Seq(ToWorkingIR, CheckHighForm, ResolveKinds, InferTypes, CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input clock: Clock
        |    mem m:
        |      data-type => Analog<2>
        |      depth => 4
        |      read-latency => 0
        |      write-latency => 1
        |      read-under-write => undefined""".stripMargin
    intercept[CheckTypes.IllegalAnalogDeclaration] {
      passes.foldLeft(parse(input)) { (c: Circuit, p: Pass) =>
        p.run(c)
      }
    }
  }

  "Declaring node with analog types" should "throw an exception" in {
    val passes = Seq(ToWorkingIR, CheckHighForm, ResolveKinds, InferTypes, CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input in: Analog<2>
        |    node n = in """.stripMargin
    intercept[CheckTypes.IllegalAnalogDeclaration] {
      passes.foldLeft(parse(input)) { (c: Circuit, p: Pass) =>
        p.run(c)
      }
    }
  }

  "Attaching a non-analog expression" should "not be ok" in {
    val passes = Seq(ToWorkingIR, CheckHighForm, ResolveKinds, InferTypes, CheckTypes)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input source: UInt<2>
        |    inst a of A
        |    inst b of B
        |    attach (source, a.o, b.o)
        |  extmodule A :
        |    output o: Analog<2>
        |  extmodule B:
        |    input o: Analog<2>""".stripMargin
    intercept[CheckTypes.OpNotAnalog] {
      passes.foldLeft(parse(input)) { (c: Circuit, p: Pass) =>
        p.run(c)
      }
    }
  }

  "Inequal attach widths" should "throw an exception" in {
    val passes = Seq(ToWorkingIR, CheckHighForm, ResolveKinds, InferTypes, CheckTypes, new InferWidths, CheckWidths)
    val input =
      """circuit Unit :
        |  module Unit :
        |    input i: Analog<3>
        |    inst a of A
        |    attach (i, a.o)
        |  extmodule A :
        |    output o: Analog<2> """.stripMargin
    intercept[CheckWidths.AttachWidthsNotEqual] {
      passes.foldLeft(CircuitState(parse(input), UnknownForm)) { (c: CircuitState, p: Transform) =>
        p.runTransform(c)
      }
    }
  }
}
