// See LICENSE for license details.

package chiselTests

import chisel3._
import chisel3.util.{Queue, EnqIO, DeqIO, QueueIO, log2Ceil}
import chisel3.experimental.{CloneModuleAsRecord, IO}
import chisel3.testers.BasicTester

class MultiIOQueue[T <: Data](gen: T, val entries: Int) extends MultiIOModule {
  val clk = IO(Input(Clock()))
  val rst = IO(Input(Reset()))
  val enq = IO(Flipped(EnqIO(gen)))
  val deq = IO(Flipped(DeqIO(gen)))
  val count = IO(Output(UInt(log2Ceil(entries + 1).W)))
  val impl = withClockAndReset(clk, rst) { Module(new Queue(gen, entries)) }
  impl.io.enq <> enq
  deq <> impl.io.deq
  count := impl.io.count
}

class QueueClone(multiIO: Boolean = false) extends Module {
  val io = IO(new QueueIO(UInt(32.W), 4))
  if (multiIO) {
    val q1 = Module(new MultiIOQueue(UInt(32.W), 2))
    val q2_io = CloneModuleAsRecord(q1)
    q1.clk := clock
    q1.rst := reset
    q1.enq <> io.enq
    q2_io("clk").asInstanceOf[Clock] := clock
    q2_io("rst").asInstanceOf[Reset] := reset
    q2_io("enq").asInstanceOf[q1.enq.type] <> q1.deq
    io.deq <> q2_io("deq").asInstanceOf[q1.deq.type]
    io.count := q1.count + q2_io("count").asInstanceOf[q1.count.type]
  } else {
    val q1 = Module(new Queue(UInt(32.W), 2))
    val q2_io = CloneModuleAsRecord(q1)
    q1.io.enq <> io.enq
    val q2_io_bundle = q2_io("io").asInstanceOf[q1.io.type]
    q2_io_bundle.enq <> q1.io.deq
    io.deq <> q2_io_bundle.deq
    io.count := q1.io.count + q2_io_bundle.count
  }
}

class QueueCloneTester(x: Int, multiIO: Boolean = false) extends BasicTester {
  val dut = Module(new QueueClone(multiIO))
  val start = RegNext(dut.io.enq.fire, true.B)
  val accept = RegNext(dut.io.deq.valid, false.B)
  dut.io.enq.bits := x.U
  dut.io.enq.valid := start
  dut.io.deq.ready := accept
  when (dut.io.deq.fire) {
    assert(dut.io.deq.bits === x.U)
    stop()
  }
}

class CloneModuleSpec extends ChiselPropSpec {

  val xVals = Table(
    ("x"),  // First tuple defines column names
    (42),   // Subsequent tuples define the data
    (63),
    (99))

  property("QueueCloneTester should return the correct result") {
    forAll (xVals) { (x: Int) =>
      assertTesterPasses{ new QueueCloneTester(x) }
    }
  }

  property("QueueClone's cloned queues should share the same module") {
    val c = Driver.toFirrtl(Driver.elaborate(() => new QueueClone))
    assert(c.modules.length == 2)
  }

  property("Clone of MultiIOModule should simulate correctly") {
    forAll (xVals) { (x: Int) =>
      assertTesterPasses{ new QueueCloneTester(x, multiIO=true) }
    }
  }

  property("Clones of MultiIOModules should share the same module") {
    val c = Driver.toFirrtl(Driver.elaborate(() => new QueueClone(multiIO=true)))
    assert(c.modules.length == 3)
  }

}
