// SPDX-License-Identifier: Apache-2.0

package chiselTests

import scala.language.reflectiveCalls
import _root_.firrtl.ir.{Closed, Open}
import chisel3._
import chisel3.internal.firrtl.{IntervalRange, KnownBinaryPoint}
import chisel3.internal.sourceinfo.{SourceInfo, UnlocatableSourceInfo}
import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage}
import chisel3.testers.BasicTester
import cookbook.CookbookTester
import firrtl.options.TargetDirAnnotation
import firrtl.passes.CheckTypes.InvalidConnect
import firrtl.passes.CheckWidths.{DisjointSqueeze, InvalidRange}
import firrtl.passes.{PassExceptions, WrapWithRemainder}
import firrtl.stage.{CompilerAnnotation, FirrtlCircuitAnnotation}
import firrtl.{
  HighFirrtlCompiler,
  LowFirrtlCompiler,
  MiddleFirrtlCompiler,
  MinimumVerilogCompiler,
  NoneCompiler,
  SystemVerilogCompiler,
  VerilogCompiler
}
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers

//noinspection TypeAnnotation

object IntervalTestHelper {

  /** Compiles a Chisel Module to Verilog
    * NOTE: This uses the "test_run_dir" as the default directory for generated code.
    * @param compilerName the generator for the module
    * @param gen the generator for the module
    * @return the Verilog code as a string.
    */
  def makeFirrtl[T <: RawModule](compilerName: String)(gen: () => T): String = {
    (new ChiselStage)
      .execute(
        Array("--compiler", compilerName, "--target-dir", "test_run_dir/IntervalSpec"),
        Seq(ChiselGeneratorAnnotation(gen))
      )
      .collectFirst { case FirrtlCircuitAnnotation(source) => source } match {
      case Some(circuit) => circuit.serialize
      case _ =>
        throw new Exception(
          s"makeFirrtl($compilerName) failed to generate firrtl circuit"
        )
    }

  }
}

import chiselTests.IntervalTestHelper.makeFirrtl
import chisel3.experimental._
import chisel3.experimental.Interval

class IntervalTest1 extends Module {
  val io = IO(new Bundle {
    val in1 = Input(Interval(range"[0,4]"))
    val in2 = Input(Interval(range"[0,4].3"))
    val out = Output(Interval(range"[0,8].3"))
  })

  io.out := io.in1 + io.in2
}

class IntervalTester extends CookbookTester(10) {

  val dut = Module(new IntervalTest1)

  dut.io.in1 := BigInt(4).I
  dut.io.in2 := 4.I
  assert(dut.io.out === 8.I)

  val i = Interval(range"[0,10)")
  stop()
}

class IntervalTest2 extends Module {
  val io = IO(new Bundle {
    val p = Input(Bool())
    val in1 = Input(Interval(range"[0,4]"))
    val in2 = Input(Interval(range"[0,6]"))
    val out = Output(Interval())
  })

  io.out := Mux(io.p, io.in1, io.in2)
}

class IntervalTester2 extends CookbookTester(10) {

  val dut = Module(new IntervalTest2)

  dut.io.p := 1.U
  dut.io.in1 := 4.I
  dut.io.in2 := 5.I
  assert(dut.io.out === 4.I)

  stop()
}

class IntervalAddTester extends BasicTester {

  val in1 = Wire(Interval(range"[0,4]"))
  val in2 = Wire(Interval(range"[0,4]"))

  in1 := 2.I
  in2 := 2.I

  5.U

  val result = in1 +& in2

  assert(result === 4.I)

  stop()

}

class IntervalSetBinaryPointTester extends BasicTester {
  implicit val sourceinfo: SourceInfo = UnlocatableSourceInfo
  val in1 = Wire(Interval(range"[0,4].4"))
  val in2 = in1.setPrecision(2)

  assert(in2.binaryPoint == KnownBinaryPoint(2))

  in1 := 2.I

  val shiftedLeft = in1.increasePrecision(2)

  assert(
    shiftedLeft.binaryPoint == KnownBinaryPoint(6),
    s"Error: increasePrecision result ${shiftedLeft.range} expected bt = 2"
  )

  val shiftedRight = in1.decreasePrecision(2)

  assert(
    shiftedRight.binaryPoint == KnownBinaryPoint(2),
    s"Error: increasePrecision result ${shiftedRight.range} expected bt = 2"
  )

  stop()
}

class MoreIntervalShiftTester extends BasicTester {
  implicit val sourceinfo: SourceInfo = UnlocatableSourceInfo

  val in1 = Wire(Interval(range"[0,4].4"))
  val in2 = in1.setPrecision(2)

  assert(in2.binaryPoint == KnownBinaryPoint(2))

  val toShiftLeft = Wire(Interval(range"[0,4].4"))
  val shiftedLeft = in1.increasePrecision(2)

  assert(
    shiftedLeft.binaryPoint == KnownBinaryPoint(2),
    s"Error: decreasePrecision result ${shiftedLeft.range} expected bt = 2"
  )

  val toShiftRight = Wire(Interval(range"[0,4].4"))
  val shiftedRight = in1.decreasePrecision(2)

  assert(
    shiftedRight.binaryPoint == KnownBinaryPoint(6),
    s"Error: decreasePrecision result ${shiftedRight.range} expected bt = 2"
  )

  stop()
}

/**
  * This is a reality check not a test. Makes it easier to figure out
  * what is going on in other places
  * @param range        a range for inputs
  * @param targetRange  a range for outputs
  * @param startNum     start here
  * @param endNum       end here
  * @param incNum       increment by this
  */
class ClipSqueezeWrapDemo(
  range:       IntervalRange,
  targetRange: IntervalRange,
  startNum:    Double,
  endNum:      Double,
  incNum:      Double)
    extends BasicTester {

  val binaryPointAsInt = range.binaryPoint.asInstanceOf[KnownBinaryPoint].value
//  val startValue = Interval.fromDouble(startNum, binaryPoint = binaryPointAsInt)
//  val increment = Interval.fromDouble(incNum, binaryPoint = binaryPointAsInt)
//  val endValue = Interval.fromDouble(endNum, binaryPoint = binaryPointAsInt)
  val startValue = startNum.I(range.binaryPoint)
  val increment = incNum.I(range.binaryPoint)
  val endValue = endNum.I(range.binaryPoint)

  val counter = RegInit(Interval(range), startValue)

  counter := (counter + increment).squeeze(counter)
  when(counter > endValue) {
    stop()
  }

  val clipped = counter.clip(0.U.asInterval(targetRange))
  val squeezed = counter.squeeze(0.U.asInterval(targetRange))
  val wrapped = counter.wrap(0.U.asInterval(targetRange))

  when(counter === startValue) {
    printf(cf"Target range is $range\n")
    printf("value     clip      squeeze      wrap\n")
  }

  printf(
    "       %d       %d          %d         %d\n",
    counter.asSInt,
    clipped.asSInt,
    squeezed.asSInt,
    wrapped.asSInt
  )
}

class SqueezeFunctionalityTester(range: IntervalRange, startNum: BigDecimal, endNum: BigDecimal, increment: BigDecimal)
    extends BasicTester {

  val counter = RegInit(0.U(10.W))
  counter := counter + 1.U
  when(counter > 10.U) {
    stop()
  }

  val squeezeInterval = Wire(Interval(range))
  squeezeInterval := 0.I

  val squeezeTemplate = Wire(Interval(range))

  val ss = WireInit(Interval(range), (-10).S.asInterval(range))

  val toSqueeze = counter.asInterval(range) - ss

  squeezeTemplate := toSqueeze.squeeze(squeezeInterval)

  printf(
    cf"SqueezeTest $counter%d ${toSqueeze.asSInt}%d.squeeze($range) => ${squeezeTemplate.asSInt}%d\n"
  )
}

/**
  * Demonstrate a simple counter register with an Interval type
  */
class IntervalRegisterTester extends BasicTester {

  val range = range"[-2,5]"
  val counter = RegInit(Interval(range), (-1).I)
  counter := (counter + 1.I)
    .squeeze(counter) // this works with other types, why not Interval
  when(counter > 4.I) {
    stop()
  }
}

//noinspection ScalaStyle
class IntervalWrapTester extends BasicTester {

  val t1 = Wire(Interval(range"[-2, 12]"))
  t1 := (-2).I
  val u1 = 0.U(3.W)
  val r1 = RegInit(u1)
  r1 := u1
  val t2 = t1.wrap(u1)
  val t3 = t1.wrap(r1)

  assert(
    t2.range.upper == Closed(7),
    s"t1 upper ${t2.range.upper} expected ${Closed(7)}"
  )
  assert(
    t3.range.upper == Closed(7),
    s"t1 upper ${t3.range.upper} expected ${Closed(7)}"
  )

  val in1 = WireInit(Interval(range"[0,9].6"), 0.I)
  val in2 = WireInit(Interval(range"[1,6).4"), 2.I)
  val in3 = in1.wrap(in2)

  assert(
    in3.range.lower == Closed(1),
    s"in3 lower ${in3.range.lower} expected ${Closed(1)}"
  )
  assert(
    in3.range.upper == Open(6),
    s"in3 upper ${in3.range.upper} expected ${Open(6)}"
  )
  assert(
    in3.binaryPoint == KnownBinaryPoint(6),
    s"in3 binaryPoint ${in3.binaryPoint} expected ${KnownBinaryPoint(2)}"
  )

  val enclosedRange = range"[-2, 5]"
  val base = Wire(Interval(range"[-4, 6]"))
  val enclosed = WireInit(Interval(enclosedRange), 0.I)
  val enclosing = WireInit(Interval(range"[-6, 8]"), 0.I)
  val overlapLeft = WireInit(Interval(range"[-10,-2]"), (-3).I)
  val overlapRight = WireInit(Interval(range"[-1,10]"), 0.I)

  val w1 = base.wrap(enclosed)
  val w2 = base.wrap(enclosing)
  val w3 = base.wrap(overlapLeft)
  val w4 = base.wrap(overlapRight)
  val w7 = base.wrap(enclosedRange)

  base := 6.I

  assert(w1 === (-2).I)
  assert(w2 === 6.I)
  assert(w3 === (-3).I)
  assert(w4 === 6.I)
  assert(w7 === (-2).I)

  stop()
}

class IntervalClipTester extends BasicTester {

  val enclosedRange = range"[-2, 5]"
  val base = Wire(Interval(range"[-4, 6]"))
  val enclosed = Wire(Interval(enclosedRange))
  val enclosing = Wire(Interval(range"[-6, 8]"))
  val overlapLeft = Wire(Interval(range"[-10,-2]"))
  val overlapRight = Wire(Interval(range"[-1,10]"))
  val disjointLeft = Wire(Interval(range"[-14,-7]"))
  val disjointRight = Wire(Interval(range"[7,11]"))

  enclosed := DontCare
  enclosing := DontCare
  overlapLeft := DontCare
  overlapRight := DontCare
  disjointLeft := DontCare
  disjointRight := DontCare

  val enclosedResult = base.clip(enclosed)
  val enclosingResult = base.clip(enclosing)
  val overlapLeftResult = base.clip(overlapLeft)
  val overlapRightResult = base.clip(overlapRight)
  val disjointLeftResult = base.clip(disjointLeft)
  val disjointRightResult = base.clip(disjointRight)
  val enclosedViaRangeString = base.clip(enclosedRange)

  base := 6.I

  assert(enclosedResult === 5.I)
  assert(enclosingResult === 6.I)
  assert(overlapLeftResult === (-2).I)
  assert(overlapRightResult === 6.I)
  assert(disjointLeftResult === (-7).I)
  assert(disjointRightResult === 7.I)

  assert(enclosedViaRangeString === 5.I)

  stop()
}

class IntervalChainedAddTester extends BasicTester {

  val intervalResult = Wire(Interval())
  val uintResult = Wire(UInt())

  intervalResult := 1.I + 1.I + 1.I + 1.I + 1.I + 1.I + 1.I
  uintResult := 1.U +& 1.U +& 1.U +& 1.U +& 1.U +& 1.U +& 1.U

  assert(intervalResult === 7.I)
  assert(uintResult === 7.U)
  stop()
}

class IntervalChainedMulTester extends BasicTester {

  val intervalResult = Wire(Interval())
  val uintResult = Wire(UInt())

  intervalResult := 2.I * 2.I * 2.I * 2.I * 2.I * 2.I * 2.I
  uintResult := 2.U * 2.U * 2.U * 2.U * 2.U * 2.U * 2.U

  assert(intervalResult === 128.I)
  assert(uintResult === 128.U)
  stop()
}

class IntervalChainedSubTester extends BasicTester {
  val intervalResult1 = Wire(Interval())
  val intervalResult2 = Wire(Interval())
  val uIntResult = Wire(UInt())
  val sIntResult = Wire(SInt())
  val fixedResult = Wire(FixedPoint())

  intervalResult1 := 17.I - 2.I - 2.I - 2.I - 2.I - 2.I - 2.I // gives same result as -& operand version below
  intervalResult2 := 17.I -& 2.I -& 2.I -& 2.I -& 2.I -& 2.I -& 2.I
  uIntResult := 17.U -& 2.U -& 2.U -& 2.U -& 2.U -& 2.U -& 2.U
  fixedResult := 17.0.F(0.BP) -& 2.0.F(0.BP) -& 2.0.F(0.BP) -& 2.0.F(0.BP) -& 2.0
    .F(0.BP) -& 2.0.F(0.BP) -& 2.0.F(0.BP)
  sIntResult := 17.S -& 2.S -& 2.S -& 2.S -& 2.S -& 2.S -& 2.S

  assert(uIntResult === 5.U)
  assert(sIntResult === 5.S)
  assert(fixedResult.asUInt === 5.U)
  assert(intervalResult1 === 5.I)
  assert(intervalResult2 === 5.I)

  val negativeInterval = (-3.5).I(4.BP)
  val positiveInterval = 3.5.I(4.BP)

  assert(negativeInterval =/= positiveInterval)
  assert(-negativeInterval === positiveInterval)
  assert(negativeInterval === -positiveInterval)

  stop()
}

//TODO: need tests for dynamic shifts on intervals
class IntervalSpec extends AnyFreeSpec with Matchers with ChiselRunners {

  type TempFirrtlException = Exception

  "Test a simple interval add" in {
    assertTesterPasses { new IntervalAddTester }
  }
  "Intervals can be created" in {
    assertTesterPasses { new IntervalTester }
  }
  "Test a simple interval mux" in {
    assertTesterPasses { new IntervalTester2 }
  }
  "Intervals can have binary points set" in {
    assertTesterPasses { new IntervalSetBinaryPointTester }
  }
  "Interval literals that don't fit in explicit ranges are caught by chisel" - {
    "case 1: does not fit in specified width" in {
      intercept[ChiselException] {
        ChiselGeneratorAnnotation(() =>
          new BasicTester {
            val x = 5.I(3.W, 0.BP)
          }
        ).elaborate
      }
    }
    "case 2: doesn't fit in specified range" in {
      intercept[ChiselException] {
        ChiselGeneratorAnnotation(() =>
          new BasicTester {
            val x = 5.I(range"[0,4]")
          }
        ).elaborate
      }
    }
  }

  "Interval literals support to double and to BigDecimal" in {
    val d = -7.125
    val lit1 = d.I(3.BP)
    lit1.litToDouble should be(d)

    val d2 = BigDecimal("1232123213131123.125")
    val lit2 = d2.I(3.BP)
    lit2.litToBigDecimal should be(d2)

    // Numbers that are too big will throw exception
    intercept[ChiselException] {
      lit2.litToDouble
    }
  }

  "Interval literals creation handles edge cases" - {
    "value at closed boundaries works" in {
      val inputRange = range"[-6, 6].2"
      val in1 = (-6.0).I(inputRange)
      val in2 = 6.0.I(inputRange)
      BigDecimal(in1.litValue) / (1 << inputRange.binaryPoint.get) should be(-6)
      BigDecimal(in2.litValue) / (1 << inputRange.binaryPoint.get) should be(6)
      intercept[ChiselException] {
        (-6.25).I(inputRange)
      }
      intercept[ChiselException] {
        (6.25).I(inputRange)
      }
    }
    "value at open boundaries works" in {
      val inputRange = range"(-6, 6).2"
      val in1 = (-5.75).I(inputRange)
      val in2 = 5.75.I(inputRange)
      BigDecimal(in1.litValue) / (1 << inputRange.binaryPoint.get) should be(-5.75)
      BigDecimal(in2.litValue) / (1 << inputRange.binaryPoint.get) should be(5.75)
      intercept[ChiselException] {
        (-6.0).I(inputRange)
      }
      intercept[ChiselException] {
        (6.0).I(inputRange)
      }
    }
    "values not precisely at open boundaries works but are converted to nearest match" in {
      val inputRange = range"(-6, 6).2"
      val in1 = (-5.95).I(inputRange)
      val in2 = 5.95.I(inputRange)
      BigDecimal(in1.litValue) / (1 << inputRange.binaryPoint.get) should be(-5.75)
      BigDecimal(in2.litValue) / (1 << inputRange.binaryPoint.get) should be(5.75)
      intercept[ChiselException] {
        (-6.1).I(inputRange)
      }
      intercept[ChiselException] {
        (6.1).I(inputRange)
      }
    }
  }

  "Let's take a look at the results of squeeze over small range" in {
    assertTesterPasses {
      new ClipSqueezeWrapDemo(
        range = range"[-10,33].0",
        targetRange = range"[-4,17].0",
        startNum = -4.0,
        endNum = 30.0,
        incNum = 1.0
      )
    }
    assertTesterPasses {
      new ClipSqueezeWrapDemo(
        range = range"[-2,5].1",
        targetRange = range"[-1,3].1",
        startNum = -2.0,
        endNum = 5.0,
        incNum = 0.5
      )
    }
  }
  "Intervals can be squeezed into another intervals range" in {
    assertTesterPasses {
      new SqueezeFunctionalityTester(
        range"[-2,5]",
        BigDecimal(-10),
        BigDecimal(10),
        BigDecimal(1.0)
      )
    }
  }
  "Intervals can be wrapped with wrap operator" in {
    assertTesterPasses { new IntervalWrapTester }
  }

  "Interval compile pathologies: clip, wrap, and squeeze have different behavior" - {
    "wrap target range is completely left of source" in {
      intercept[TempFirrtlException] {
        assertTesterPasses(new BasicTester {
          val base = Wire(Interval(range"[-4, 6]"))
          base := 6.I
          val disjointLeft = WireInit(Interval(range"[-7,-5]"), (-6).I)
          val w5 = base.wrap(disjointLeft)
          stop()
        })
      }
    }
    "wrap target range is completely right of source" in {
      intercept[TempFirrtlException] {
        assertTesterPasses(new BasicTester {
          val base = Wire(Interval(range"[-4, 6]"))
          base := 6.I
          val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I)
          val w5 = base.wrap(disjointLeft)
          stop()
        })
      }
    }
    "clip target range is completely left of source" in {
      assertTesterPasses(new BasicTester {
        val base = Wire(Interval(range"[-4, 6]"))
        base := 6.I
        val disjointLeft = WireInit(Interval(range"[-7,-5]"), (-6).I)
        val w5 = base.clip(disjointLeft)
        chisel3.assert(w5 === (-5).I)
        stop()
      })
    }
    "clip target range is completely right of source" in {
      assertTesterPasses(new BasicTester {
        val base = Wire(Interval(range"[-4, 6]"))
        base := 6.I
        val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I)
        val w5 = base.clip(disjointLeft)
        chisel3.assert(w5.asSInt === 7.S)
        stop()
      })
    }
    "squeeze target range is completely right of source" in {
      intercept[TempFirrtlException] {
        assertTesterPasses(new BasicTester {
          val base = Wire(Interval(range"[-4, 6]"))
          base := 6.I
          val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I)
          val w5 = base.squeeze(disjointLeft)
          chisel3.assert(w5.asSInt === 6.S)
          stop()
        })
      }
    }
    "squeeze target range is completely left of source" in {
      intercept[TempFirrtlException] {
        assertTesterPasses(new BasicTester {
          val base = Wire(Interval(range"[-4, 6]"))
          base := 6.I
          val disjointLeft = WireInit(Interval(range"[-7, -5]"), 8.I)
          val w5 = base.squeeze(disjointLeft)
          stop()
        })
      }
    }

    def makeCircuit(operation: String, sourceRange: IntervalRange, targetRange: IntervalRange): () => RawModule = {
      () =>
        new Module {
          val io = IO(new Bundle { val out = Output(Interval()) })
          val base = Wire(Interval(sourceRange))
          base := 6.I

          val disjointLeft = WireInit(Interval(targetRange), 8.I)
          val w5 = operation match {
            case "clip"    => base.clip(disjointLeft)
            case "wrap"    => base.wrap(disjointLeft)
            case "squeeze" => base.squeeze(disjointLeft)
          }
          io.out := w5
        }
    }

    "disjoint ranges should error when used with clip, wrap and squeeze" - {

      def mustGetException(disjointLeft: Boolean, operation: String): Boolean = {
        val (rangeA, rangeB) = if (disjointLeft) {
          (range"[-4, 6]", range"[7,10]")
        } else {
          (range"[7,10]", range"[-4, 6]")
        }
        try {
          makeFirrtl("low")(makeCircuit(operation, rangeA, rangeB))
          false
        } catch {
          case _: InvalidConnect | _: PassExceptions | _: InvalidRange | _: WrapWithRemainder | _: DisjointSqueeze =>
            true
          case _: Throwable =>
            false
        }
      }

      "Range A disjoint left, operation clip should generate useful error" in {
        mustGetException(disjointLeft = true, "clip") should be(false)
      }
      "Range A largely out of bounds left, operation wrap should generate useful error" in {
        mustGetException(disjointLeft = true, "wrap") should be(true)
      }
      "Range A disjoint left, operation squeeze should generate useful error" in {
        mustGetException(disjointLeft = true, "squeeze") should be(true)
      }
      "Range A disjoint right, operation clip should generate useful error" in {
        mustGetException(disjointLeft = false, "clip") should be(true)
      }
      "Range A disjoint right, operation wrap should generate useful error" in {
        mustGetException(disjointLeft = false, "wrap") should be(true)
      }
      "Range A disjoint right, operation squeeze should generate useful error" in {
        mustGetException(disjointLeft = false, "squeeze") should be(true)
      }
    }

    "Errors are sometimes inconsistent or incorrectly labelled as Firrtl Internal Error" - {
      "squeeze disjoint is not internal error when defined in BasicTester" in {
        intercept[DisjointSqueeze] {
          makeFirrtl("low")(() =>
            new BasicTester {
              val base = Wire(Interval(range"[-4, 6]"))
              val base2 = Wire(Interval(range"[-4, 6]"))
              base := 6.I
              base2 := 5.I
              val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I)
              val w5 = base.squeeze(disjointLeft)
              stop()
            }
          )
        }
      }
      "wrap disjoint is not internal error when defined in BasicTester" in {
        intercept[DisjointSqueeze] {
          makeFirrtl("low")(() =>
            new BasicTester {
              val base = Wire(Interval(range"[-4, 6]"))
              val base2 = Wire(Interval(range"[-4, 6]"))
              base := 6.I
              base2 := 5.I
              val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I)
              val w5 = base.squeeze(disjointLeft)
              stop()
            }
          )
        }
      }
      "squeeze disjoint from Module gives exception" in {
        intercept[DisjointSqueeze] {
          makeFirrtl("low")(() =>
            new Module {
              val io = IO(new Bundle {
                val out = Output(Interval())
              })
              val base = Wire(Interval(range"[-4, 6]"))
              base := 6.I

              val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I)
              val w5 = base.squeeze(disjointLeft)
              io.out := w5
            }
          )
        }
      }
      "clip disjoint from Module gives no error" in {
        makeFirrtl("low")(() =>
          new Module {
            val io = IO(new Bundle {
              val out = Output(Interval())
            })
            val base = Wire(Interval(range"[-4, 6]"))
            base := 6.I

            val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I)
            val w5 = base.clip(disjointLeft)
            io.out := w5
          }
        )
      }
      "wrap disjoint from Module wrap with remainder" in {
        intercept[WrapWithRemainder] {
          makeFirrtl("low")(() =>
            new Module {
              val io = IO(new Bundle {
                val out = Output(Interval())
              })
              val base = Wire(Interval(range"[-4, 6]"))
              base := 6.I

              val disjointLeft = WireInit(Interval(range"[7,10]"), 8.I)
              val w5 = base.wrap(disjointLeft)
              io.out := w5
            }
          )
        }
      }
    }

    "assign literal out of range of interval" in {
      intercept[firrtl.passes.CheckTypes.InvalidConnect] {
        assertTesterPasses(new BasicTester {
          val base = Wire(Interval(range"[-4, 6]"))
          base := (-8).I
        })
      }
    }
  }

  "Intervals should catch assignment of literals outside of range" - {
    "when literal is too small" in {
      intercept[InvalidConnect] {
        makeFirrtl("low")(() =>
          new Module {
            val io = IO(new Bundle { val out = Output(Interval()) })
            val base = Wire(Interval(range"[-4, 6]"))
            base := (-7).I
            io.out := base
          }
        )
      }
    }
    "when literal is too big" in {
      intercept[InvalidConnect] {
        makeFirrtl("low")(() =>
          new Module {
            val io = IO(new Bundle { val out = Output(Interval()) })
            val base = Wire(Interval(range"[-4, 6]"))
            base := 9.I
            io.out := base
          }
        )
      }
    }
  }

  "Intervals can be shifted left" in {
    assertTesterPasses(new BasicTester {
      val i1 = 3.0.I(range"[0,4]")
      val shifted1 = i1 << 2
      val shiftUInt = WireInit(1.U(8.W))
      val shifted2 = i1 << shiftUInt

      chisel3.assert(shifted1 === 12.I, "shifted 1 should be 12, it wasn't")
      chisel3.assert(shifted2 === 6.I, "shifted 2 should be 6 it wasn't")
      stop()
    })
  }

  "Intervals can be shifted right" in {
    assertTesterPasses(new BasicTester {
      val i1 = 12.0.I(range"[0,15]")
      val shifted1 = i1 >> 2
      val shiftUInt = 1.U
      val shifted2 = i1 >> shiftUInt

      chisel3.assert(shifted1 === 3.I)
      chisel3.assert(shifted2 === 6.I)
      stop()
    })
  }

  "Intervals can be used to construct registers" in {
    assertTesterPasses { new IntervalRegisterTester }
  }
  "Intervals can be clipped with clip (saturate) operator" in {
    assertTesterPasses { new IntervalClipTester }
  }
  "Intervals adds same answer as UInt" in {
    assertTesterPasses { new IntervalChainedAddTester }
  }
  "Intervals should produce canonically smaller ranges via inference" in {
    val loFirrtl = makeFirrtl("low")(() =>
      new Module {
        val io = IO(new Bundle {
          val in = Input(Interval(range"[0,1]"))
          val out = Output(Interval())
        })

        val intervalResult = Wire(Interval())

        intervalResult := 1.I + 1.I + 1.I + 1.I + 1.I + 1.I + 1.I
        io.out := intervalResult
      }
    )
    loFirrtl.contains("output io_out : SInt<4>") should be(true)

  }
  "Intervals multiplication same answer as UInt" in {
    assertTesterPasses { new IntervalChainedMulTester }
  }
  "Intervals subs same answer as UInt" in {
    assertTesterPasses { new IntervalChainedSubTester }
  }
  "Test clip, wrap and a variety of ranges" - {
    """range"[0.0,10.0].2" => range"[2,6].2"""" in {
      assertTesterPasses(new BasicTester {

        val sourceRange = range"[0.0,10.0].2"
        val targetRange = range"[2,6].2"

        val sourceSimulator = ScalaIntervalSimulator(sourceRange)
        val targetSimulator = ScalaIntervalSimulator(targetRange)

        for (sourceValue <- sourceSimulator.allValues) {
          val clippedValue = Wire(Interval(targetRange))
          clippedValue := sourceSimulator
            .makeLit(sourceValue)
            .clip(clippedValue)

          val goldClippedValue =
            targetSimulator.makeLit(targetSimulator.clip(sourceValue))

          // Useful for debugging
          // printf(s"source value $sourceValue clipped gold value %d compare to clipped value %d\n",
          //  goldClippedValue.asSInt, clippedValue.asSInt)

          chisel3.assert(goldClippedValue === clippedValue)

          val wrappedValue = Wire(Interval(targetRange))
          wrappedValue := sourceSimulator
            .makeLit(sourceValue)
            .wrap(wrappedValue)

          val goldWrappedValue =
            targetSimulator.makeLit(targetSimulator.wrap(sourceValue))

          // Useful for debugging
          // printf(s"source value $sourceValue wrapped gold value %d compare to wrapped value %d\n",
          //  goldWrappedValue.asSInt, wrappedValue.asSInt)

          chisel3.assert(goldWrappedValue === wrappedValue)
        }

        stop()
      })
    }
  }

  "Test squeeze over a variety of ranges" - {
    """range"[2,6].2""" in {
      assertTesterPasses(new BasicTester {

        val sourceRange = range"[0.0,10.0].2"
        val targetRange = range"[2,6].3"

        val sourceSimulator = ScalaIntervalSimulator(sourceRange)
        val targetSimulator = ScalaIntervalSimulator(targetRange)

        for (sourceValue <- sourceSimulator.allValues) {
          val squeezedValue = Wire(Interval(targetRange))
          squeezedValue := sourceSimulator
            .makeLit(sourceValue)
            .clip(squeezedValue)

          val goldSqueezedValue =
            targetSimulator.makeLit(targetSimulator.clip(sourceValue))

          // Useful for debugging
          // printf(s"source value $sourceValue squeezed gold value %d compare to squeezed value %d\n",
          //   goldSqueezedValue.asSInt, squeezedValue.asSInt)

          chisel3.assert(goldSqueezedValue === squeezedValue)
        }

        stop()
      })
    }
  }

  "test asInterval" - {
    "use with UInt" in {
      assertTesterPasses(new BasicTester {
        val u1 = Wire(UInt(5.W))
        u1 := 7.U
        val i1 = u1.asInterval(range"[0,15]")
        val i2 = u1.asInterval(range"[0,15].2")
        printf("i1 %d\n", i1.asUInt)
        chisel3.assert(i1 === 7.I, "i1")
        stop()
      })
    }
    "use with SInt" in {
      assertTesterPasses(new BasicTester {
        val s1 = Wire(SInt(5.W))
        s1 := 7.S
        val s2 = Wire(SInt(5.W))
        s2 := 7.S
        val i1 = s1.asInterval(range"[-16,15]")
        val i2 = s1.asInterval(range"[-16,15].1")
        printf("i1 %d\n", i1.asSInt)
        printf("i2 %d\n", i2.asSInt)
        chisel3.assert(i1 === 7.I, "i1 is wrong")
        chisel3.assert(i2 === (3.5).I(binaryPoint = 1.BP), "i2 is wrong")
        stop()
      })
    }
    "more SInt tests" in {
      assertTesterPasses(new BasicTester {
        chisel3.assert(7.S.asInterval(range"[-16,15].1") === 3.5.I(binaryPoint = 1.BP), "adding binary point")
        stop()
      })
    }
  }
}
