// SPDX-License-Identifier: Apache-2.0

package chiselTests

import chisel3._
import chisel3.stage.ChiselStage
import chisel3.util.{Counter, Queue}
import chisel3.testers.BasicTester
import firrtl.checks.CheckResets.NonLiteralAsyncResetValueException

class AsyncResetTester extends BasicTester {
  val (_, cDiv) = Counter(true.B, 4)
  // First rising edge when count === 3
  val slowClk = cDiv.asClock

  val (count, done) = Counter(true.B, 16)

  val asyncResetNext = RegInit(false.B)
  asyncResetNext := count === 4.U
  val asyncReset = asyncResetNext.asAsyncReset

  val reg = withClockAndReset(slowClk, asyncReset) {
    RegInit(123.U(8.W))
  }
  reg := 5.U // Normal connection

  when(count === 3.U) {
    assert(reg === 5.U)
  }
  when(count >= 5.U && count < 7.U) {
    assert(reg === 123.U)
  }.elsewhen(count >= 7.U) {
    assert(reg === 5.U)
  }

  when(done) {
    stop()
  }
}

class AsyncResetAggregateTester extends BasicTester {
  class MyBundle extends Bundle {
    val x = UInt(8.W)
    val y = UInt(8.W)
  }
  val (_, cDiv) = Counter(true.B, 4)
  // First rising edge when count === 3
  val slowClk = cDiv.asClock

  val (count, done) = Counter(true.B, 16)

  val asyncResetNext = RegInit(false.B)
  asyncResetNext := count === 4.U
  val asyncReset = asyncResetNext.asAsyncReset

  val reg = withClockAndReset(slowClk, asyncReset) {
    val init = Wire(Vec(2, new MyBundle))
    init(0).x := 0.U
    init(0).y := 0.U
    init(1).x := 0.U
    init(1).y := 0.U
    RegInit(init)
  }
  reg(0).x := 5.U // Normal connections
  reg(0).y := 6.U
  reg(1).x := 7.U
  reg(1).y := 8.U

  when(count === 3.U) {
    assert(reg(0).x === 5.U)
    assert(reg(0).y === 6.U)
    assert(reg(1).x === 7.U)
    assert(reg(1).y === 8.U)
  }
  when(count >= 5.U && count < 7.U) {
    assert(reg(0).x === 0.U)
    assert(reg(0).y === 0.U)
    assert(reg(1).x === 0.U)
    assert(reg(1).y === 0.U)
  }.elsewhen(count >= 7.U) {
    assert(reg(0).x === 5.U)
    assert(reg(0).y === 6.U)
    assert(reg(1).x === 7.U)
    assert(reg(1).y === 8.U)
  }

  when(done) {
    stop()
  }
}

class AsyncResetQueueTester extends BasicTester {
  val (_, cDiv) = Counter(true.B, 4)
  val slowClk = cDiv.asClock

  val (count, done) = Counter(true.B, 16)

  val asyncResetNext = RegNext(false.B, false.B)
  val asyncReset = asyncResetNext.asAsyncReset

  val queue = withClockAndReset(slowClk, asyncReset) {
    Module(new Queue(UInt(8.W), 4))
  }
  queue.io.enq.valid := true.B
  queue.io.enq.bits := count

  queue.io.deq.ready := false.B

  val doCheck = RegNext(false.B, false.B)
  when(queue.io.count === 3.U) {
    asyncResetNext := true.B
    doCheck := true.B
  }
  when(doCheck) {
    assert(queue.io.count === 0.U)
  }

  when(done) {
    stop()
  }
}

class AsyncResetDontCareModule extends RawModule {
  import chisel3.util.Valid
  val monoPort = IO(Output(AsyncReset()))
  monoPort := DontCare
  val monoWire = Wire(AsyncReset())
  monoWire := DontCare
  val monoAggPort = IO(Output(Valid(AsyncReset())))
  monoAggPort := DontCare
  val monoAggWire = Wire(Valid(AsyncReset()))
  monoAggWire := DontCare

  // Can't bulk connect to Wire so only ports here
  val bulkPort = IO(Output(AsyncReset()))
  bulkPort <> DontCare
  val bulkAggPort = IO(Output(Valid(AsyncReset())))
  bulkAggPort <> DontCare
}

class AsyncResetSpec extends ChiselFlatSpec with Utils {

  behavior.of("AsyncReset")

  it should "be able to be connected to DontCare" in {
    ChiselStage.elaborate(new AsyncResetDontCareModule)
  }

  it should "be allowed with literal reset values" in {
    ChiselStage.elaborate(new BasicTester {
      withReset(reset.asAsyncReset)(RegInit(123.U))
    })
  }

  it should "NOT be allowed with non-literal reset values" in {
    a[NonLiteralAsyncResetValueException] should be thrownBy extractCause[NonLiteralAsyncResetValueException] {
      compile(new BasicTester {
        val x = WireInit(123.U + 456.U)
        withReset(reset.asAsyncReset)(RegInit(x))
      })
    }
  }

  it should "NOT be allowed to connect directly to a Bool" in {
    a[ChiselException] should be thrownBy extractCause[ChiselException] {
      ChiselStage.elaborate(new BasicTester {
        val bool = Wire(Bool())
        val areset = reset.asAsyncReset
        bool := areset
      })
    }
  }

  it should "simulate correctly" in {
    assertTesterPasses(new AsyncResetTester)
  }

  it should "simulate correctly with aggregates" in {
    assertTesterPasses(new AsyncResetAggregateTester)
  }

  it should "allow casting to and from Bool" in {
    ChiselStage.elaborate(new BasicTester {
      val r: Reset = reset
      val a: AsyncReset = WireInit(r.asAsyncReset)
      val b: Bool = a.asBool
      val c: AsyncReset = b.asAsyncReset
    })
  }

  it should "allow changing the reset type of whole modules like Queue" in {
    assertTesterPasses(new AsyncResetQueueTester)
  }

  it should "support SInt regs" in {
    assertTesterPasses(new BasicTester {
      // Also check that it traces through wires
      val initValue = Wire(SInt())
      val reg = withReset(reset.asAsyncReset)(RegNext(initValue, 27.S))
      initValue := -43.S
      val (count, done) = Counter(true.B, 4)
      when(count === 0.U) {
        chisel3.assert(reg === 27.S)
      }.otherwise {
        chisel3.assert(reg === -43.S)
      }
      when(done) { stop() }
    })
  }

  it should "support Fixed regs" in {
    assertTesterPasses(new BasicTester {
      val reg = withReset(reset.asAsyncReset)(RegNext(-6.0.F(2.BP), 3.F(2.BP)))
      val (count, done) = Counter(true.B, 4)
      when(count === 0.U) {
        chisel3.assert(reg === 3.F(2.BP))
      }.otherwise {
        chisel3.assert(reg === -6.0.F(2.BP))
      }
      when(done) { stop() }
    })
  }

  it should "support Interval regs" in {
    import chisel3.experimental._
    assertTesterPasses(new BasicTester {
      val reg = withReset(reset.asAsyncReset) {
        val x = RegInit(Interval(range"[0,13]"), 13.I)
        x := 7.I
        x
      }
      val (count, done) = Counter(true.B, 4)
      when(count === 0.U) {
        chisel3.assert(reg === 13.I)
      }.otherwise {
        chisel3.assert(reg === 7.I)
      }
      when(done) { stop() }
    })
  }

  it should "allow literals cast to Bundles as reset values" in {
    class MyBundle extends Bundle {
      val x = UInt(16.W)
      val y = UInt(16.W)
    }
    assertTesterPasses(new BasicTester {
      val reg = withReset(reset.asAsyncReset) {
        RegNext(0xbad0cad0L.U.asTypeOf(new MyBundle), 0xdeadbeefL.U.asTypeOf(new MyBundle))
      }
      val (count, done) = Counter(true.B, 4)
      when(count === 0.U) {
        chisel3.assert(reg.asUInt === 0xdeadbeefL.U)
      }.otherwise {
        chisel3.assert(reg.asUInt === 0xbad0cad0L.U)
      }
      when(done) { stop() }
    })
  }
  it should "allow literals cast to Vecs as reset values" in {
    assertTesterPasses(new BasicTester {
      val reg = withReset(reset.asAsyncReset) {
        RegNext(0xbad0cad0L.U.asTypeOf(Vec(4, UInt(8.W))), 0xdeadbeefL.U.asTypeOf(Vec(4, UInt(8.W))))
      }
      val (count, done) = Counter(true.B, 4)
      when(count === 0.U) {
        chisel3.assert(reg.asUInt === 0xdeadbeefL.U)
      }.otherwise {
        chisel3.assert(reg.asUInt === 0xbad0cad0L.U)
      }
      when(done) { stop() }
    })
  }
}
