// SPDX-License-Identifier: Apache-2.0

package firrtlTests

import firrtl._
import firrtl.passes._
import firrtl.testutils._
import annotations._
import wiring._

class WiringTests extends FirrtlFlatSpec {
  private def executeTest(input: String, expected: String, passes: Seq[Transform], annos: Seq[Annotation]): Unit = {
    val c = passes
      .foldLeft(CircuitState(Parser.parse(input.split("\n").toIterator), UnknownForm, annos)) {
        (c: CircuitState, p: Transform) => p.runTransform(c)
      }
      .circuit

    (parse(c.serialize).serialize) should be(parse(expected).serialize)
  }

  private def executeTest(input: String, expected: String, passes: Seq[Transform]): Unit = {
    executeTest(input, expected, passes, Seq.empty)
  }

  def passes = Seq(
    ToWorkingIR,
    ResolveKinds,
    InferTypes,
    ResolveFlows,
    new InferWidths
  )

  it should "wire from a register source (r) to multiple extmodule sinks (X)" in {
    val sinks = Seq(ModuleName("X", CircuitName("Top")))
    val source = ComponentName("r", ModuleName("C", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    inst b of B
         |    b.clock <= clock
         |    inst x of X
         |    x.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |  module B :
         |    input clock: Clock
         |    inst c of C
         |    c.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |  module C :
         |    input clock: Clock
         |    reg r: UInt<5>, clock
         |  module D :
         |    input clock: Clock
         |    inst x1 of X
         |    x1.clock <= clock
         |    inst x2 of X
         |    x2.clock <= clock
         |  extmodule X :
         |    input clock: Clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire r: UInt<5>
         |    inst b of B
         |    b.clock <= clock
         |    inst x of X
         |    x.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |    x.pin <= r
         |    r <= b.r
         |    d.r <= r
         |  module B :
         |    input clock: Clock
         |    output r: UInt<5>
         |    inst c of C
         |    c.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |    r <= c.r_0
         |    d.r <= r
         |  module C :
         |    input clock: Clock
         |    output r_0: UInt<5>
         |    reg r: UInt<5>, clock
         |    r_0 <= r
         |  module D :
         |    input clock: Clock
         |    input r: UInt<5>
         |    inst x1 of X
         |    x1.clock <= clock
         |    inst x2 of X
         |    x2.clock <= clock
         |    x1.pin <= r
         |    x2.pin <= r
         |  extmodule X :
         |    input clock: Clock
         |    input pin: UInt<5>
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "wire from a register source (r) to multiple module sinks (X)" in {
    val sinks = Seq(ModuleName("X", CircuitName("Top")))
    val source = ComponentName("r", ModuleName("C", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    inst b of B
         |    b.clock <= clock
         |    inst x of X
         |    x.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |  module B :
         |    input clock: Clock
         |    inst c of C
         |    c.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |  module C :
         |    input clock: Clock
         |    reg r: UInt<5>, clock
         |  module D :
         |    input clock: Clock
         |    inst x1 of X
         |    x1.clock <= clock
         |    inst x2 of X
         |    x2.clock <= clock
         |  module X :
         |    input clock: Clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire r: UInt<5>
         |    inst b of B
         |    b.clock <= clock
         |    inst x of X
         |    x.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |    x.pin <= r
         |    r <= b.r
         |    d.r <= r
         |  module B :
         |    input clock: Clock
         |    output r: UInt<5>
         |    inst c of C
         |    c.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |    r <= c.r_0
         |    d.r <= r
         |  module C :
         |    input clock: Clock
         |    output r_0: UInt<5>
         |    reg r: UInt<5>, clock
         |    r_0 <= r
         |  module D :
         |    input clock: Clock
         |    input r: UInt<5>
         |    inst x1 of X
         |    x1.clock <= clock
         |    inst x2 of X
         |    x2.clock <= clock
         |    x1.pin <= r
         |    x2.pin <= r
         |  module X :
         |    input clock: Clock
         |    input pin: UInt<5>
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "wire from a register sink (r) to a wire source (s) in another module (X)" in {
    val sinks = Seq(ComponentName("s", ModuleName("X", CircuitName("Top"))))
    val source = ComponentName("r", ModuleName("C", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    inst b of B
         |    b.clock <= clock
         |    inst x of X
         |    x.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |  module B :
         |    input clock: Clock
         |    inst c of C
         |    c.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |  module C :
         |    input clock: Clock
         |    reg r: UInt<5>, clock
         |  module D :
         |    input clock: Clock
         |    inst x1 of X
         |    x1.clock <= clock
         |    inst x2 of X
         |    x2.clock <= clock
         |  module X :
         |    input clock: Clock
         |    wire s: UInt<5>
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire r: UInt<5>
         |    inst b of B
         |    b.clock <= clock
         |    inst x of X
         |    x.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |    x.pin <= r
         |    r <= b.r
         |    d.r <= r
         |  module B :
         |    input clock: Clock
         |    output r: UInt<5>
         |    inst c of C
         |    c.clock <= clock
         |    inst d of D
         |    d.clock <= clock
         |    r <= c.r_0
         |    d.r <= r
         |  module C :
         |    input clock: Clock
         |    output r_0: UInt<5>
         |    reg r: UInt<5>, clock
         |    r_0 <= r
         |  module D :
         |    input clock: Clock
         |    input r: UInt<5>
         |    inst x1 of X
         |    x1.clock <= clock
         |    inst x2 of X
         |    x2.clock <= clock
         |    x1.pin <= r
         |    x2.pin <= r
         |  module X :
         |    input clock: Clock
         |    input pin: UInt<5>
         |    wire s: UInt<5>
         |    s <= pin
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "wire from a SubField source (r.x) to an extmodule sink (X)" in {
    val sinks = Seq(ModuleName("X", CircuitName("Top")))
    val source = ComponentName("r.x", ModuleName("A", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    reg r : {x: UInt<5>}, clock
         |    inst x of X
         |    x.clock <= clock
         |  extmodule X :
         |    input clock: Clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire r_x: UInt<5>
         |    reg r: {x: UInt<5>}, clock
         |    inst x of X
         |    x.clock <= clock
         |    x.pin <= r_x
         |    r_x <= r.x
         |  extmodule X :
         |    input clock: Clock
         |    input pin: UInt<5>
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "wire properly with a source as a submodule of a sink" in {
    val sinks = Seq(ComponentName("s", ModuleName("A", CircuitName("Top"))))
    val source = ComponentName("r", ModuleName("X", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire s: UInt<5>
         |    inst x of X
         |    x.clock <= clock
         |  module X :
         |    input clock: Clock
         |    reg r: UInt<5>, clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire pin: UInt<5>
         |    wire s: UInt<5>
         |    inst x of X
         |    x.clock <= clock
         |    pin <= x.r_0
         |    s <= pin
         |  module X :
         |    input clock: Clock
         |    output r_0: UInt<5>
         |    reg r: UInt<5>, clock
         |    r_0 <= r
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "wire with source and sink in the same module" in {
    val sinks = Seq(ComponentName("s", ModuleName("Top", CircuitName("Top"))))
    val source = ComponentName("r", ModuleName("Top", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    wire s: UInt<5>
         |    reg r: UInt<5>, clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    wire pin: UInt<5>
         |    wire s: UInt<5>
         |    reg r: UInt<5>, clock
         |    s <= pin
         |    pin <= r
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "wire multiple sinks in the same module" in {
    val sinks = Seq(
      ComponentName("s", ModuleName("A", CircuitName("Top"))),
      ComponentName("t", ModuleName("A", CircuitName("Top")))
    )
    val source = ComponentName("r", ModuleName("A", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire s: UInt<5>
         |    wire t: UInt<5>
         |    reg r: UInt<5>, clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire pin: UInt<5>
         |    wire s: UInt<5>
         |    wire t: UInt<5>
         |    reg r: UInt<5>, clock
         |    t <= pin
         |    s <= pin
         |    pin <= r
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "wire clocks" in {
    val sinks = Seq(ModuleName("X", CircuitName("Top")))
    val source = ComponentName("clock", ModuleName("A", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    inst x of X
         |    x.clock <= clock
         |  extmodule X :
         |    input clock: Clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire clock_0: Clock
         |    inst x of X
         |    x.clock <= clock
         |    x.pin <= clock_0
         |    clock_0 <= clock
         |  extmodule X :
         |    input clock: Clock
         |    input pin: Clock
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "handle two source instances with clearly defined sinks" in {
    val sinks = Seq(ModuleName("X", CircuitName("Top")))
    val source = ComponentName("clock", ModuleName("A", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a1 of A
         |    a1.clock <= clock
         |    inst a2 of A
         |    a2.clock <= clock
         |  module A :
         |    input clock: Clock
         |    inst x of X
         |    x.clock <= clock
         |  extmodule X :
         |    input clock: Clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a1 of A
         |    a1.clock <= clock
         |    inst a2 of A
         |    a2.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire clock_0: Clock
         |    inst x of X
         |    x.clock <= clock
         |    x.pin <= clock_0
         |    clock_0 <= clock
         |  extmodule X :
         |    input clock: Clock
         |    input pin: Clock
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "wire multiple clocks" in {
    val sinks = Seq(ModuleName("X", CircuitName("Top")))
    val source = ComponentName("clock", ModuleName("A", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a1 of A
         |    a1.clock <= clock
         |    inst a2 of A
         |    a2.clock <= clock
         |  module A :
         |    input clock: Clock
         |    inst x of X
         |    x.clock <= clock
         |  extmodule X :
         |    input clock: Clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a1 of A
         |    a1.clock <= clock
         |    inst a2 of A
         |    a2.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire clock_0: Clock
         |    inst x of X
         |    x.clock <= clock
         |    x.pin <= clock_0
         |    clock_0 <= clock
         |  extmodule X :
         |    input clock: Clock
         |    input pin: Clock
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "error with WiringException for indeterminate ownership" in {
    val sinks = Seq(ModuleName("X", CircuitName("Top")))
    val source = ComponentName("clock", ModuleName("A", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a1 of A
         |    a1.clock <= clock
         |    inst a2 of A
         |    a2.clock <= clock
         |    inst x of X
         |    x.clock <= clock
         |  module A :
         |    input clock: Clock
         |    inst x of X
         |    x.clock <= clock
         |  extmodule X :
         |    input clock: Clock
         |""".stripMargin

    intercept[WiringException] {
      val wiringPass = new Wiring(Seq(sas))
      executeTest(input, "", passes :+ wiringPass)
    }
  }

  it should "wire subindex source to sink" in {
    val sinks = Seq(ModuleName("X", CircuitName("Top")))
    val source = ComponentName("r[a]", ModuleName("A", CircuitName("Top")))
    val sas = WiringInfo(source, sinks, "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    reg r: UInt<2>[5], clock
         |    node a = UInt(5)
         |    inst x of X
         |    x.clock <= clock
         |  extmodule X :
         |    input clock: Clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire r_a: UInt<2>
         |    reg r: UInt<2>[5], clock
         |    node a = UInt(5)
         |    inst x of X
         |    x.clock <= clock
         |    x.pin <= r_a
         |    r_a <= r[a]
         |  extmodule X :
         |    input clock: Clock
         |    input pin: UInt<2>
         |""".stripMargin

    val wiringPass = new Wiring(Seq(sas))
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "wire using Annotations with a sink module" in {
    val source = SourceAnnotation(ComponentName("r", ModuleName("Top", CircuitName("Top"))), "pin")
    val sink = SinkAnnotation(ModuleName("X", CircuitName("Top")), "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clk: Clock
         |    inst x of X
         |    x.clk <= clk
         |    reg r: UInt<5>, clk
         |  extmodule X :
         |    input clk: Clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clk: Clock
         |    wire r_0 : UInt<5>
         |    inst x of X
         |    x.clk <= clk
         |    reg r: UInt<5>, clk
         |    x.pin <= r_0
         |    r_0 <= r
         |  extmodule X :
         |    input clk: Clock
         |    input pin: UInt<5>
         |""".stripMargin

    val wiringXForm = new WiringTransform()
    executeTest(input, check, passes :+ wiringXForm, Seq(source, sink))
  }

  it should "wire using Annotations with a sink component" in {
    val source = SourceAnnotation(ComponentName("r", ModuleName("Top", CircuitName("Top"))), "pin")
    val sink = SinkAnnotation(ComponentName("s", ModuleName("X", CircuitName("Top"))), "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clk: Clock
         |    inst x of X
         |    x.clk <= clk
         |    reg r: UInt<5>, clk
         |  module X :
         |    input clk: Clock
         |    wire s: UInt<5>
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clk: Clock
         |    wire r_0 : UInt<5>
         |    inst x of X
         |    x.clk <= clk
         |    reg r: UInt<5>, clk
         |    x.pin <= r_0
         |    r_0 <= r
         |  module X :
         |    input clk: Clock
         |    input pin: UInt<5>
         |    wire s: UInt<5>
         |    s <= pin
         |""".stripMargin

    val wiringXForm = new WiringTransform()
    executeTest(input, check, passes :+ wiringXForm, Seq(source, sink))
  }

  it should "wire using annotations with Aggregate source" in {
    val source = SourceAnnotation(ComponentName("bundle", ModuleName("A", CircuitName("Top"))), "pin")
    val sink = SinkAnnotation(ModuleName("B", CircuitName("Top")), "pin")
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock : Clock
         |    inst a of A
         |    inst b of B
         |    a.clock <= clock
         |    b.clock <= clock
         |  module A :
         |    input clock : Clock
         |    wire bundle : {x : UInt<1>, y: UInt<1>, z: {zz : UInt<1>} }
         |    bundle is invalid
         |  module B :
         |    input clock : Clock""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock : Clock
         |    wire bundle : {x : UInt<1>, y: UInt<1>, z: {zz : UInt<1>} }
         |    inst a of A
         |    inst b of B
         |    a.clock <= clock
         |    b.clock <= clock
         |    b.pin <= bundle
         |    bundle <= a.bundle_0
         |  module A :
         |    input clock : Clock
         |    output bundle_0 : {x : UInt<1>, y: UInt<1>, z: {zz : UInt<1>} }
         |    wire bundle : {x : UInt<1>, y: UInt<1>, z: {zz : UInt<1>} }
         |    bundle is invalid
         |    bundle_0 <= bundle
         |  module B :
         |    input clock : Clock
         |    input pin : {x : UInt<1>, y: UInt<1>, z: {zz : UInt<1>} }""".stripMargin

    val wiringXForm = new WiringTransform()
    executeTest(input, check, passes :+ wiringXForm, Seq(source, sink))
  }

  it should "wire one sink to multiple, disjoint extmodules" in {
    val sinkX = Seq(ModuleName("X", CircuitName("Top")))
    val sourceX = ComponentName("r.x", ModuleName("A", CircuitName("Top")))
    val sinkY = Seq(ModuleName("Y", CircuitName("Top")))
    val sourceY = ComponentName("r.x", ModuleName("A", CircuitName("Top")))
    val wiSeq = Seq(WiringInfo(sourceX, sinkX, "pin"), WiringInfo(sourceY, sinkY, "pin"))
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    reg r : {x: UInt<5>}, clock
         |    inst x of X
         |    x.clock <= clock
         |    inst y of Y
         |    y.clock <= clock
         |  extmodule X :
         |    input clock: Clock
         |  extmodule Y :
         |    input clock: Clock
         |""".stripMargin
    val check =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    inst a of A
         |    a.clock <= clock
         |  module A :
         |    input clock: Clock
         |    wire r_x_0: UInt<5>
         |    wire r_x: UInt<5>
         |    reg r: {x: UInt<5>}, clock
         |    inst x of X
         |    x.clock <= clock
         |    inst y of Y
         |    y.clock <= clock
         |    x.pin <= r_x
         |    r_x <= r.x
         |    y.pin <= r_x_0
         |    r_x_0 <= r.x
         |  extmodule X :
         |    input clock: Clock
         |    input pin: UInt<5>
         |  extmodule Y :
         |    input clock: Clock
         |    input pin: UInt<5>
         |""".stripMargin

    val wiringPass = new Wiring(wiSeq)
    executeTest(input, check, passes :+ wiringPass)
  }

  it should "error when there are multiple sources for the same pin" in {
    val sink = ComponentName("s", ModuleName("Top", CircuitName("Top")))
    val source1 = ComponentName("r", ModuleName("Top", CircuitName("Top")))
    val source2 = ComponentName("r2", ModuleName("Top", CircuitName("Top")))
    val annos = Seq(SourceAnnotation(source1, "pin"), SourceAnnotation(source2, "pin"), SinkAnnotation(sink, "pin"))
    val input =
      """|circuit Top :
         |  module Top :
         |    input clock: Clock
         |    wire s: UInt<5>
         |    reg r: UInt<5>, clock
         |    reg r2: UInt<5>, clock
         |""".stripMargin
    a[WiringException] shouldBe thrownBy {
      executeTest(input, "", passes :+ new WiringTransform, annos)
    }
  }
}
