// See LICENSE for license details.

package firrtlTests

import firrtl._
import firrtl.annotations._
import firrtl.testutils._
import firrtl.testutils.FirrtlCheckers._

class PresetSpec extends FirrtlFlatSpec {
  type Mod = Seq[String]
  type ModuleSeq = Seq[Mod]
  def compile(input: String, annos: AnnotationSeq): CircuitState =
    (new VerilogCompiler).compileAndEmit(CircuitState(parse(input), ChirrtlForm, annos), List.empty)
  def compileBody(modules: ModuleSeq) = {
    val annos = Seq(new PresetAnnotation(CircuitTarget("Test").module("Test").ref("reset")), firrtl.transforms.NoDCEAnnotation)
    var str = """
      |circuit Test :
      |""".stripMargin
    modules foreach ((m: Mod) => {
      val header = "|module " + m(0) + " :"
      str +=  header.stripMargin.stripMargin.split("\n").mkString("  ", "\n  ", "")
      str += m(1).split("\n").mkString("    ", "\n    ", "")
      str += """
        |""".stripMargin
    })
    compile(str,annos)
  }

  "Preset" should """behave properly given a `Preset` annotated `AsyncReset` INPUT reset:
    - replace AsyncReset specific blocks by standard Register blocks 
    - add inline declaration of all registers connected to reset
    - remove the useless input port""" in {
    val result = compileBody(Seq(Seq("Test",s"""
      |input clock : Clock
      |input reset : AsyncReset
      |input x : UInt<1>
      |output z : UInt<1>
      |reg r : UInt<1>, clock with : (reset => (reset, UInt(0)))
      |r <= x
      |z <= r""".stripMargin))
    )
    result shouldNot containLine ("always @(posedge clock or posedge reset) begin")
    result shouldNot containLines (
      "if (reset) begin",
      "r = 1'h0;",
      "end")
    result shouldNot containLine ("input   reset,")
    result should containLine ("always @(posedge clock) begin")
    result should containLine ("reg  r = 1'h0;")
  }
  
  it should """behave properly given a `Preset` annotated `AsyncReset` WIRE reset:
    - replace AsyncReset specific blocks by standard Register blocks 
    - add inline declaration of all registers connected to reset
    - remove the useless wire declaration and assignation""" in {
    val result = compileBody(Seq(Seq("Test",s"""
      |input clock : Clock
      |input x : UInt<1>
      |output z : UInt<1>
      |wire reset : AsyncReset
      |reset <= asAsyncReset(UInt(0))
      |reg r : UInt<1>, clock with : (reset => (reset, UInt(0)))
      |r <= x
      |z <= r""".stripMargin))
    )
    result shouldNot containLine ("always @(posedge clock or posedge reset) begin")
    result shouldNot containLines (
      "if (reset) begin",
      "r = 1'h0;",
      "end")
    result should containLine ("always @(posedge clock) begin")
    result should containLine ("reg  r = 1'h0;")
    // it should also remove useless asyncReset signal, all along the path down to registers 
    result shouldNot containLine ("wire  reset;")
    result shouldNot containLine ("assign reset = 1'h0;")
  }
  it should "raise TreeCleanUpOrphantException on cast of annotated AsyncReset" in {
    an [firrtl.transforms.PropagatePresetAnnotations.TreeCleanUpOrphanException] shouldBe thrownBy {
      compileBody(Seq(Seq("Test",s"""
        |input clock : Clock
        |input x : UInt<1>
        |output z : UInt<1>
        |output sz : UInt<1>
        |wire reset : AsyncReset
        |reset <= asAsyncReset(UInt(0))
        |reg r : UInt<1>, clock with : (reset => (reset, UInt(0)))
        |wire sreset : UInt<1>
        |sreset <= asUInt(reset) ; this is FORBIDDEN
        |reg s : UInt<1>, clock with : (reset => (sreset, UInt(0)))
        |r <= x
        |s <= x
        |z <= r
        |sz <= s""".stripMargin))
      )
    }
  }
  
  it should "propagate through bundles" in {
    val result = compileBody(Seq(Seq("Test",s"""
      |input clock : Clock
      |input reset : AsyncReset
      |input x : UInt<1>
      |output z : UInt<1>
      |wire bundle : {in_rst: AsyncReset, out_rst:AsyncReset}
      |bundle.in_rst <= reset
      |bundle.out_rst <= bundle.in_rst
      |reg r : UInt<1>, clock with : (reset => (bundle.out_rst, UInt(0)))
      |r <= x
      |z <= r""".stripMargin))
    )
    result shouldNot containLine ("input   reset,")
    result shouldNot containLine ("always @(posedge clock or posedge reset) begin")
    result shouldNot containLines (
      "if (reset) begin",
      "r = 1'h0;",
      "end")
    result should containLine ("always @(posedge clock) begin")
    result should containLine ("reg  r = 1'h0;")
  }
  it should "propagate through vectors" in {
    val result = compileBody(Seq(Seq("Test",s"""
      |input clock : Clock
      |input reset : AsyncReset
      |input x : UInt<1>
      |output z : UInt<1>
      |wire vector : AsyncReset[2]
      |vector[0] <= reset
      |vector[1] <= vector[0]
      |reg r : UInt<1>, clock with : (reset => (vector[1], UInt(0)))
      |r <= x
      |z <= r""".stripMargin))
    )
    result shouldNot containLine ("input   reset,")
    result shouldNot containLine ("always @(posedge clock or posedge reset) begin")
    result shouldNot containLines (
      "if (reset) begin",
      "r = 1'h0;",
      "end")
    result should containLine ("always @(posedge clock) begin")
    result should containLine ("reg  r = 1'h0;")
  }
  
  it should "propagate through bundles of vectors" in {
    val result = compileBody(Seq(Seq("Test",s"""
      |input clock : Clock
      |input reset : AsyncReset
      |input x : UInt<1>
      |output z : UInt<1>
      |wire bundle : {in_rst: AsyncReset[2], out_rst:AsyncReset}
      |bundle.in_rst[0] <= reset
      |bundle.in_rst[1] <= bundle.in_rst[0]
      |bundle.out_rst <= bundle.in_rst[1]
      |reg r : UInt<1>, clock with : (reset => (bundle.out_rst, UInt(0)))
      |r <= x
      |z <= r""".stripMargin))
    )
    result shouldNot containLine ("input   reset,")
    result shouldNot containLine ("always @(posedge clock or posedge reset) begin")
    result shouldNot containLines (
      "if (reset) begin",
      "r = 1'h0;",
      "end")
    result should containLine ("always @(posedge clock) begin")
    result should containLine ("reg  r = 1'h0;")
  }
  it should """propagate properly accross modules: 
    - replace AsyncReset specific blocks by standard Register blocks 
    - add inline declaration of all registers connected to reset
    - remove the useless input port of instanciated module
    - remove the useless instance connections 
    - remove wires and assignations used in instance connections
    """ in {
    val result = compileBody(Seq(
      Seq("TestA",s"""
      |input clock : Clock
      |input reset : AsyncReset
      |input x : UInt<1>
      |output z : UInt<1>
      |reg r : UInt<1>, clock with : (reset => (reset, UInt(0)))
      |r <= x
      |z <= r
      |""".stripMargin), 
      Seq("Test",s"""
      |input clock : Clock
      |input x : UInt<1>
      |output z : UInt<1>
      |wire reset : AsyncReset
      |reset <= asAsyncReset(UInt(0))
      |inst i of TestA
      |i.clock <= clock
      |i.reset <= reset
      |i.x <= x
      |z <= i.z""".stripMargin)
    ))
    // assess that all useless connections are not emitted
    result shouldNot containLine ("wire i_reset;")
    result shouldNot containLine (".reset(i_reset),")
    result shouldNot containLine ("assign i_reset = reset;")
    result shouldNot containLine ("input   reset,")
    
    result shouldNot containLine ("always @(posedge clock or posedge reset) begin")
    result shouldNot containLines (
      "if (reset) begin",
      "r = 1'h0;",
      "end")
    result should containLine ("always @(posedge clock) begin")
    result should containLine ("reg  r = 1'h0;")
  }

  it should "propagate even through disordonned statements" in {
    val result = compileBody(Seq(Seq("Test",s"""
      |input clock : Clock
      |input reset : AsyncReset
      |input x : UInt<1>
      |output z : UInt<1>
      |wire bundle : {in_rst: AsyncReset, out_rst:AsyncReset}
      |reg r : UInt<1>, clock with : (reset => (bundle.out_rst, UInt(0)))
      |bundle.out_rst <= bundle.in_rst
      |bundle.in_rst <= reset
      |r <= x
      |z <= r""".stripMargin))
    )
    result shouldNot containLine ("input   reset,")
    result shouldNot containLine ("always @(posedge clock or posedge reset) begin")
    result shouldNot containLines (
      "if (reset) begin",
      "r = 1'h0;",
      "end")
    result should containLine ("always @(posedge clock) begin")
    result should containLine ("reg  r = 1'h0;")
  }

}

class PresetExecutionTest extends ExecutionTest(
  "PresetTester", 
  "/features", 
  annotations = Seq(new PresetAnnotation(CircuitTarget("PresetTester").module("PresetTester").ref("preset")))
)
