package icenet

import chisel3._
import chisel3.util._
import freechips.rocketchip.util.LatencyPipe
import scala.math.max
import IceNetConsts._

class PacketGen(lengths: Seq[Int], genData: Seq[BigInt]) extends Module {
  val io = IO(new Bundle {
    val start = Input(Bool())
    val finished = Output(Bool())
    val out = Decoupled(new StreamChannel(NET_IF_WIDTH))
  })

  val maxLength = lengths.reduce(max(_, _))
  val totalLength = lengths.reduce(_ + _)
  val lengthVec = VecInit(lengths.map(_.U))
  val dataVec = VecInit(genData.map(_.U(NET_IF_WIDTH.W)))

  require(totalLength == genData.size)

  val pktIdx = Reg(UInt(log2Ceil(lengths.size).W))
  val pktOffset = Reg(UInt(log2Ceil(maxLength).W))
  val dataIdx = Reg(UInt(log2Ceil(totalLength).W))
  val sending = RegInit(false.B)
  val started = RegInit(false.B)

  when (!sending && io.start) {
    started := true.B
    sending := true.B
    pktIdx := 0.U
    pktOffset := 0.U
    dataIdx := 0.U
  }

  when (io.out.fire) {
    dataIdx := dataIdx + 1.U
    pktOffset := pktOffset + 1.U
    when (io.out.bits.last) {
      pktIdx := pktIdx + 1.U
      pktOffset := 0.U
      when (pktIdx === (lengths.size - 1).U) {
        sending := false.B
      }
    }
  }

  io.out.valid := sending
  io.out.bits.data := dataVec(dataIdx)
  io.out.bits.keep := NET_FULL_KEEP
  io.out.bits.last := pktOffset === lengthVec(pktIdx) - 1.U
  io.finished := started && !sending
}

class PacketCheck(
    checkData: Seq[BigInt],
    checkKeep: Seq[Int],
    checkLast: Seq[Boolean]) extends Module {
  val io = IO(new Bundle {
    val in = Flipped(Decoupled(new StreamChannel(NET_IF_WIDTH)))
    val finished = Output(Bool())
  })

  val checkDataVec = VecInit(checkData.map(_.U(NET_IF_WIDTH.W)))
  val checkKeepVec = VecInit(checkKeep.map(_.U(NET_IF_BYTES.W)))
  val checkLastVec = VecInit(checkLast.map(_.B))

  val (checkIdx, checkDone) = Counter(io.in.fire, checkDataVec.length)

  val finished = RegInit(false.B)

  io.in.ready := !finished
  io.finished := finished

  def compareData(a: UInt, b: UInt, keep: UInt) = {
    val bitmask = FillInterleaved(8, keep)
    (a & bitmask) === (b & bitmask)
  }

  assert(!io.in.valid ||
    (compareData(io.in.bits.data, checkDataVec(checkIdx), io.in.bits.keep) &&
      io.in.bits.keep === checkKeepVec(checkIdx) &&
      io.in.bits.last === checkLastVec(checkIdx)),
    "PacketCheck: input does not match")

}

class NetDelay(latency: Int) extends Module {
  val io = IO(new Bundle {
    val left = new StreamIO(NET_IF_WIDTH)
    val right = Flipped(new StreamIO(NET_IF_WIDTH))
  })

  io.left.out <> LatencyPipe(io.right.out, latency)
  io.right.in <> LatencyPipe(io.left.in,   latency)
}

object NetDelay {
  def apply(right: StreamIO, latency: Int): StreamIO = {
    val delay = Module(new NetDelay(latency))
    delay.io.right.out <> right.out
    right.in <> delay.io.right.in
    delay.io.left
  }
}


package icenet

import chisel3._
import chisel3.util._
import IceNetConsts.NET_IF_WIDTH

class TraceROM extends BlackBox with HasBlackBoxResource{

  val io = IO(new Bundle {
    val clock = Input(Clock())
    val reset = Input(Bool())
    val stream = Decoupled(new StreamChannel(NET_IF_WIDTH))
    val macAddr = Output(UInt(48.W))


// See README.md for license details.

package gcd

import chisel3._
import chisel3.util.Decoupled

class GcdInputBundle(val w: Int) extends Bundle {
  val value1 = UInt(w.W)
  val value2 = UInt(w.W)
}

class GcdOutputBundle(val w: Int) extends Bundle {
  val value1 = UInt(w.W)
  val value2 = UInt(w.W)
  val gcd    = UInt(w.W)
}

/**
  * Compute Gcd using subtraction method.
  * Subtracts the smaller from the larger until register y is zero.
  * value input register x is then the Gcd.
  * Unless first input is zero then the Gcd is y.
  * Can handle stalls on the producer or consumer side
  */
class DecoupledGcd(width: Int) extends MultiIOModule {
  val input = IO(Flipped(Decoupled(new GcdInputBundle(width))))
  val output = IO(Decoupled(new GcdOutputBundle(width)))

  val xInitial    = Reg(UInt())
  val yInitial    = Reg(UInt())
  val x           = Reg(UInt())
  val y           = Reg(UInt())
  val busy        = RegInit(false.B)
  val resultValid = RegInit(false.B)

  input.ready := ! busy
  output.valid := resultValid
  output.bits := DontCare

  when(busy)  {
    when(x > y) {
      x := x - y
    }.otherwise {
      y := y - x
    }
    when(x === 0.U || y === 0.U) {
      when(x === 0.U) {
        output.bits.gcd := y
      }.otherwise {
        output.bits.gcd := x
      }

      output.bits.value1 := xInitial

    when(input.valid) {
      val bundle = input.deq()
      x := bundle.value1
      y := bundle.value2
      xInitial := bundle.value1
      yInitial := bundle.value2
      busy := true.B
    }
  }
}

      output.bits.value2 := yInitial
      resultValid := true.B

      when(output.ready && resultValid) {
        busy := false.B


// See README.md for license details.

package gcd

import chisel3._

/**
  * Compute GCD using subtraction method.
  * Subtracts the smaller from the larger until register y is zero.
  * value in register x is then the GCD
  */
class GCD extends Module {
  val io = IO(new Bundle {
    val value1        = Input(UInt(16.W))
    val value2        = Input(UInt(16.W))
    val loadingValues = Input(Bool())
    val outputGCD     = Output(UInt(16.W))
    val outputValid   = Output(Bool())
  })

  val x  = Reg(UInt())
  val y  = Reg(UInt())

    .otherwise { y := y - x }

  when(io.loadingValues) {
    x := io.value1
    y := io.value2
  }

  io.outputGCD := x
  io.outputValid := y === 0.U
}


package router

import chisel3._
import chisel3.util._
import chisel3.experimental.BundleLiterals._

class RouterConfig(val nIfaces: Int = 4) extends Bundle {
  val iface = Vec(
    nIfaces,
    new Bundle {
      val mac = new MacAddr
      val ipv4 = new Ipv4Addr
      val mask = new Ipv4Addr
    }
  )
}

class WrapBits(w: Int) extends Bundle {
  val bits = Bits(w.W)
  def ===(that: WrapBits) = bits === that.bits
  def =/=(that: WrapBits) = bits =/= that.bits
}

class MacAddr extends WrapBits(48) {
  override def toPrintable: Printable = {
    asTypeOf(Vec(6, UInt(8.W))).reverse

      .reduce((a, b) => a + ":" + b)
  }
}

object MacAddr {
  def apply(s: String) =
    (new MacAddr).Lit(_.bits -> ("h" + s.replace(":", "")).U)
  def apply(bytes: Array[Byte]) =
    (new MacAddr).Lit(_.bits -> ("h" + bytes.map(b => f"$b%02x").mkString).U)
  val BROADCAST = MacAddr("ff:ff:ff:ff:ff:ff")
}

class Ipv4Addr extends WrapBits(32) {
  override def toPrintable: Printable = {
    asTypeOf(Vec(4, UInt(8.W))).reverse
      .map(b => p"$b")
      .reduce((a, b) => a + "." + b)
  }
  def &(that: Ipv4Addr) = (bits & that.bits).asTypeOf(new Ipv4Addr)
}

object Ipv4Addr {
  def apply(s: String) =
    (new Ipv4Addr).Lit(
      _.bits -> ("h" + s.split("\\.").map(d => f"${d.toInt}%02x").mkString).U
    )
  val LOCALHOST = Ipv4Addr("127.0.0.1")
}

class EthType extends WrapBits(16) {}

object EthType {
  private def apply(s: Short) = (new EthType).Lit(_.bits -> s.U)
  val ARP = EthType(0x0806)
  val IPV4 = EthType(0x0800)
}

class EtherHeader extends Bundle {
  val ethDst = new MacAddr
  val ethSrc = new MacAddr
  val ethType = new EthType
  val payload = Bits((34 * 8).W)
}

class ArpHeader extends Bundle {
  val hardwareType = UInt(16.W)
  val protocolType = new EthType
  val hardwareSize = UInt(8.W)
  val protocolSize = UInt(8.W)
  val opcode = UInt(16.W)
  val srcMac = new MacAddr
  val srcIpv4 = new Ipv4Addr
  val dstMac = new MacAddr
  val dstIpv4 = new Ipv4Addr
  val payload = Bits((6 * 8).W)

  def isValid =
    hardwareType === 1.U &&
      protocolType === EthType.IPV4 &&
      hardwareSize === 6.U &&
      protocolSize === 4.U &&
      (opcode === 1.U || opcode === 2.U)

  def setTypeSize() {
    hardwareType := 1.U
    protocolType := EthType.IPV4
    hardwareSize := 6.U
    protocolSize := 4.U
  }
}

object ArpOpcode {
  val Request = 1.U
  val Response = 2.U
}

class Ipv4Header extends Bundle {
  val version = UInt(4.W)
  val headerLen = UInt(4.W)
  val dscp_ecn = UInt(8.W)
  val totalLen = UInt(16.W)
  val id = UInt(16.W)

  val flags_reserved = UInt(1.W)
  val dontFrag = UInt(1.W)
  val moreFrag = UInt(1.W)
  val fragOffset = UInt(13.W)

  val ttl = UInt(8.W)
  val protocol = UInt(8.W)
  val checksum = UInt(16.W)
  val src = new Ipv4Addr
  val dst = new Ipv4Addr

  val payload = Bits((14 * 8).W)

  def isValid =
    version === 4.U &&
      headerLen === 5.U

  def calcChecksum() = {
    val s1 = asTypeOf(Vec(17, UInt(16.W)))
      .slice(7, 17)
      .map(_.asTypeOf(UInt(32.W)))
      .reduce((a, b) => a + b)
    val s2 = s1.head(16) +& s1.tail(16)
    s2.head(1) + s2.tail(1)
  }

  def setChecksum(x: UInt) {
    checksum := x(x.getWidth - 1, 16) + x(15, 0)
  }
}

object IpProtocol {
  val UDP = 17.U
}

class UdpHeader extends Bundle {
  val srcPort = UInt(16.W)
  val dstPort = UInt(16.W)
  val length = UInt(16.W)
  val checksum = UInt(16.W)
  val payload = Bits((6 * 8).W)
}


package router.pipeline

import chisel3._
import chisel3.util._
import router._
import router.table._

// Process ARP packet
class ArpPipeline extends Pipeline(hasArpModify = true) {

  when(io.in.valid && isFirstBeat) {
    val ethIn = io.in.bits.data.asTypeOf(new EtherHeader())
    val arpIn = ethIn.payload.asTypeOf(new ArpHeader())
    when(ethIn.ethType === EthType.ARP) {
      // by default drop this frame
      dropRest()

      when(arpIn.isValid) {
        val id = io.in.bits.id
        val iface = io.config.iface(id)
        val targetIsMe = arpIn.dstIpv4 === iface.ipv4
        // update ARP cache
        io.arpModify.get.op := Mux(targetIsMe, ArpOp.Insert, ArpOp.Update)

          val arpOut = WireInit(arpIn)
          // construct reply
          ethOut.ethSrc := iface.mac
          ethOut.ethDst := ethIn.ethSrc
          arpOut.opcode := ArpOpcode.Response
          arpOut.srcMac := iface.mac
          arpOut.dstMac := ethIn.ethSrc
          arpOut.srcIpv4 := iface.ipv4
          arpOut.dstIpv4 := arpIn.srcIpv4
          arpOut.payload := 0.U
          // output reply
          val ARP_LEN = 42
          ethOut.payload := arpOut.asUInt
          out.valid := true.B
          out.bits.id := io.in.bits.id
          out.bits.data := ethOut.asUInt
          out.bits.keep := AXIStreamData.keep(ARP_LEN, 48)
          out.bits.last := true.B
        }
      }
    }
  }
}

        io.arpModify.get.ipv4 := arpIn.srcIpv4
        io.arpModify.get.mac := arpIn.srcMac


package router.pipeline

import chisel3._
import chisel3.util._
import router._

// Check IPv4 header and drop invalid frames.
class Ipv4Check extends Pipeline {

  when(io.in.valid && isFirstBeat) {
    val ethIn = io.in.bits.data.asTypeOf(new EtherHeader())
    val ipv4In = ethIn.payload.asTypeOf(new Ipv4Header())
    when(ethIn.ethType === EthType.IPV4) {
      // FIXME: checksum causes timing error!
      // when(!ipv4In.isValid || ipv4In.calcChecksum() =/= 0xFFFF.U) {

      when(!ipv4In.isValid) {
        dropRest()
      }


package router.pipeline

import chisel3._
import chisel3.util._
import router._

// Query forward table
// update IPv4 header
// set (iface, nextHop) at eth.ethDst.
class Ipv4Forward extends Pipeline {

  when(io.in.valid && isFirstBeat) {
    val ethIn = io.in.bits.data.asTypeOf(new EtherHeader())
    val ipv4In = ethIn.payload.asTypeOf(new Ipv4Header())
    when(ethIn.ethType === EthType.IPV4) {
      // TODO: query forward table

      // to me
      for (iface <- io.config.iface) {
        when(ipv4In.dst === iface.ipv4) {
          dropRest()
        }
      }

      // default route

      for ((iface, i) <- io.config.iface.zipWithIndex) {
        when((ipv4In.dst & iface.mask) === (iface.ipv4 & iface.mask)) {
          outIface := i.U
          nextHop := ipv4In.dst
        }
      }

      // update IPv4 header
      val ipv4Out = WireInit(ipv4In)
      ipv4Out.ttl := ipv4In.ttl - 1.U
      ipv4Out.setChecksum(ipv4In.checksum +& 0x0100.U)

      // set nexthop at Eth.ethDst
      val ethDstOut = WireInit(ethIn.ethDst.asTypeOf(new NextHop))
      ethDstOut.iface := outIface
      ethDstOut.nextHop := nextHop

      // output
      val ethOut = WireInit(ethIn)
      ethOut.ethDst := ethDstOut.asTypeOf(new MacAddr)
      ethOut.payload := ipv4Out.asUInt
      out.bits.data := ethOut.asUInt
    }
  }
}

class NextHop extends Bundle {
  val iface = UInt(16.W)
  val nextHop = new Ipv4Addr
}

      val outIface = WireInit(3.U)
      val nextHop = WireInit(Ipv4Addr("10.0.3.2"))


package router.pipeline

import chisel3._
import chisel3.util._
import router._

// Query ARP cache and set ethernet header.
class Ipv4SetMac extends Pipeline(hasArpQuery = true) {

  when(io.in.valid && isFirstBeat) {
    val ethIn = io.in.bits.data.asTypeOf(new EtherHeader())
    val ipv4In = ethIn.payload.asTypeOf(new Ipv4Header())
    val nextHop = ethIn.ethDst.asTypeOf(new NextHop())
    when(ethIn.ethType === EthType.IPV4) {
      // debug print
      // printf(p"$ipv4In checksum=${ipv4In.calcChecksum()}\n")
      // when(ipv4In.protocol === IpProtocol.UDP) {
      //   val udpIn = ipv4In.payload.asTypeOf(new UdpHeader())
      //   printf(p"  $udpIn\n")
      // }

      // query ARP cache
      io.arpQuery.get.ipv4 := nextHop.nextHop
      val dstMac = io.arpQuery.get.mac

        out.bits.id := nextHop.iface
        out.bits.data := ethOut.asUInt
      }.otherwise {
        dropRest()
        // generate ARP request
        val ethOut = WireInit(ethIn)
        ethOut.ethSrc := iface.mac
        ethOut.ethDst := MacAddr.BROADCAST
        ethOut.ethType := EthType.ARP
        val arpOut = Wire(new ArpHeader)
        arpOut.setTypeSize()
        arpOut.opcode := ArpOpcode.Request
        arpOut.srcMac := iface.mac
        arpOut.dstMac := MacAddr.BROADCAST
        arpOut.srcIpv4 := iface.ipv4
        arpOut.dstIpv4 := nextHop.nextHop
        arpOut.payload := 0.U
        ethOut.payload := arpOut.asUInt

        // output
        out.valid := true.B
        out.bits.id := nextHop.iface
        out.bits.data := ethOut.asUInt
        out.bits.keep := AXIStreamData.keep(42, 48)
        out.bits.last := true.B
      }
    }
  }
}

      val iface = io.config.iface(nextHop.iface)
      when(dstMac.valid) {
        // update ethernet header
        val ethOut = WireInit(ethIn)
        ethOut.ethSrc := iface.mac


package router.pipeline

import chisel3._
import router._

// Filter out packets with the wrong destination MAC address or the wrong type
class L2Filter extends Pipeline {

  when(io.in.valid && isFirstBeat) {
    val ethIn = io.in.bits.data.asTypeOf(new EtherHeader())

    when(!(dstIsMe || ethIn.ethDst === MacAddr.BROADCAST)) {
      dropRest()
    }
    when(!(ethIn.ethType === EthType.ARP || ethIn.ethType === EthType.IPV4)) {
      dropRest()
    }
  }
}

val dstIsMe = ethIn.ethDst === io.config.iface(io.in.bits.id).mac


package router.pipeline

import chisel3._
import chisel3.util._
import router._

// Reverse src and dst MAC then send to the origin iface.
class LoopbackPipeline extends Pipeline {
  io.out <> io.in
  when(isFirstBeat) {

    val ethIn = io.in.bits.data.asTypeOf(new EtherHeader())
    // swap src and dst MAC
    val ethOut = WireDefault(ethIn)
    ethOut.ethSrc := ethIn.ethDst
    ethOut.ethDst := ethIn.ethSrc


// Definations of pipeline

package router.pipeline

import chisel3._
import chisel3.util._
import chisel3.experimental.BundleLiterals._
import router._
import router.table.{ArpModify, ArpQuery}

// The input and output of pipeline.
class AXIStreamData(val w: Int = 48 * 8) extends Bundle {
  private val wb = w / 8

  // transmited payload. only valid when both `valid` and `ready` is true.
  val data = Bits(w.W)
  // valid for each bytes in the last cycle. only valid when `last` is true.
  val keep = Bits(wb.W)
  // is the last cycle?
  val last = Bool()
  // iface ID of the packet
  val id = UInt(3.W)

  def litToBytes(): Seq[Byte] = {
    var bytes = data.litValue.toByteArray.toSeq
    bytes = if (bytes.length > wb) {
      bytes.slice(bytes.length - wb, bytes.length)
    } else if (bytes.length < wb) {

    } else {
      bytes
    }
    if (last.litToBoolean) {
      bytes.slice(0, keep.litValue().bitCount)
    } else {
      bytes
    }
  }
}

object AXIStreamData {
  // Generate keep literal
  def keep(n: Int, wb: Int) = (((BigInt(1) << n) - 1) << (wb - n)).U(wb.W)

  // Convert packet to AXIStream.
  def fromPacket(
      id: Int,
      packet: Array[Byte],
      width: Int
  ): Seq[AXIStreamData] = {
    val wb = width / 8
    packet
      .grouped(wb)
      .zipWithIndex
      .map {
        case (data, i) =>
          (new AXIStreamData(width)).Lit(
            _.data -> BigInt(
              Array(0.toByte) ++ data ++ Array.fill(wb - data.length) {
                0.toByte
              }
            ).U(width.W),
            _.keep -> keep(data.length, wb),
            _.last -> ((i + 1) * wb >= packet.length).B,
            _.id -> id.U
          )
      }
      .toSeq
  }

  // Convert AXIStream to packet.
  def toPacket(
      datas: Array[AXIStreamData]
  ): (Int, Array[Byte]) = {
    val id = datas.head.id.litValue().toInt
    val data = datas.flatMap(_.litToBytes)
    (id, data)
  }
}

class PipelineBundle extends Bundle {
  val in = Flipped(Decoupled(new AXIStreamData(48 * 8)))
  val out = Decoupled(new AXIStreamData(48 * 8))
  val config = Input(new RouterConfig())
}

class Pipeline(
    hasArpQuery: Boolean = false,
    hasArpModify: Boolean = false
) extends Module {
  val io = IO(new PipelineBundle {
    val arpQuery = if (hasArpQuery) Some(new ArpQuery) else None
    val arpModify = if (hasArpModify) Some(new ArpModify) else None
  })

  val isFirstBeat = RegInit(true.B)
  when(io.in.fire) {
    isFirstBeat := io.in.bits.last
  }

  // drop current and rest beat?
  private val drop = RegInit(false.B)
  // auto reset after last beat
  when(io.in.fire && io.in.bits.last) {
    drop := false.B
  }
  // for implementor
  def dropRest() {
    out.valid := false.B
    out.bits := DontCare
    drop := !io.in.bits.last
  }

  // output for implementor
  val out = Wire(Valid(new AXIStreamData))
  out.valid := io.in.valid
  out.bits := io.in.bits

  // default output
  io.in.ready := io.out.ready || drop
  io.out.valid := RegEnable(out.valid && !drop, false.B, io.out.ready)
  io.out.bits := RegEnable(out.bits, io.out.ready)
  if (hasArpModify) {
    io.arpModify.get.setNone()
  }
  if (hasArpQuery) {
    io.arpQuery.get.setNone()
  }
}

Seq.fill(wb - bytes.length) { 0.toByte } ++ bytes


package router.pipeline

import chisel3._
import chisel3.util._
import router._
import router.table._

class Router extends Pipeline {
  // config
  val config = Wire(new RouterConfig())
  for ((iface, i) <- config.iface.zipWithIndex) {
    iface.ipv4 := Ipv4Addr(s"10.0.$i.1")
    iface.mask := Ipv4Addr("255.255.255.0")
    iface.mac := MacAddr(s"RJGG_$i".map(_.toByte).toArray)
  }

  // the output of last pipeline
  var in = io.in
  def addPipeline[T <: Pipeline](m: T): T = {
    m.io.config <> config

    in = m.io.out
    m
  }

  val l2filter = addPipeline(Module(new L2Filter))
  val arp = addPipeline(Module(new ArpPipeline))
  val ipv4Check = addPipeline(Module(new Ipv4Check))
  val ipv4Forward = addPipeline(Module(new Ipv4Forward))
  val ipv4SetMac = addPipeline(Module(new Ipv4SetMac))
  in <> io.out

  val arpCache = Module(new ArpCache)
  arp.io.arpModify.get <> arpCache.io.modify
  ipv4SetMac.io.arpQuery.get <> arpCache.io.query
}

object Gen {
  def main(args: Array[String]): Unit = {
    chisel3.Driver.execute(args, () => new Router())
  }
}


package router.table

import chisel3._
import chisel3.util._
import router._

class ArpQuery extends Bundle {
  val ipv4 = Output(new Ipv4Addr)
  val mac = Input(Valid(new MacAddr))

  def setNone() = {
    ipv4 := Ipv4Addr("0.0.0.0")
  }
}

class ArpModify extends Bundle {
  val op = Output(UInt(3.W))
  val ipv4 = Output(new Ipv4Addr)
  val mac = Output(new MacAddr)

  def setNone() = {
    op := ArpOp.None
    ipv4 := DontCare
    mac := DontCare
  }
}

object ArpOp {
  val None = 0.U
  val Insert = 1.U
  val Update = 2.U
  val Remove = 3.U
  val Clear = 4.U
}

// ARP Cache. Direct mapping.
class ArpCache(val SIZE_LOG2: Int = 4) extends Module {
  val io = IO(new Bundle {
    val query = Flipped(new ArpQuery)
    val modify = Flipped(new ArpModify)
  })
  val SIZE = 1 << SIZE_LOG2

  class ArpEntry extends Bundle {
    val ipv4 = new Ipv4Addr
    val mac = new MacAddr
  }
  val table = Mem(SIZE, Valid(new ArpEntry))

  def hashIndex(ipv4: Ipv4Addr) =
    ipv4
      .asTypeOf(Vec(4, UInt(8.W)))
      .reduce((a, b) => a ^ b)
      .apply(SIZE_LOG2 - 1, 0)

  // reset | clear
  when(reset.asBool || io.modify.op === ArpOp.Clear) {
    for (i <- 0 until SIZE) {
      table(i).valid := false.B
    }
  }
  // query
  val key = io.query.ipv4
  val entry = table(hashIndex(key))
  io.query.mac.valid := entry.valid && entry.bits.ipv4 === key
  io.query.mac.bits := entry.bits.mac

  val idx = hashIndex(io.modify.ipv4)
  when(io.modify.op === ArpOp.Insert) {
    table(idx).valid := true.B
    table(idx).bits.ipv4 := io.modify.ipv4
    table(idx).bits.mac := io.modify.mac
  }
  when(io.modify.op === ArpOp.Update) {
    when(table(idx).valid && table(idx).bits.ipv4 === io.modify.ipv4) {
      table(idx).bits.mac := io.modify.mac
    }
  }

  when(io.modify.op === ArpOp.Remove) {
    when(table(idx).bits.ipv4 === io.modify.ipv4) {
      table(idx).valid := false.B
    }
  }


// See README.md for license details.

package gcd

import chisel3._

/**
  * This provides an alternate way to run tests, by executing then as a main
  * From sbt (Note: the test: prefix is because this main is under the test package hierarchy):
  * {{{
  * test:runMain gcd.GCDMain
  * }}}
  * To see all command line options use:
  * {{{
  * test:runMain gcd.GCDMain --help
  * }}}
  * To run with verilator:
  * {{{
  * test:runMain gcd.GCDMain --backend-name verilator
  * }}}
  * To run with verilator from your terminal shell use:
  * {{{
  * sbt 'test:runMain gcd.GCDMain --backend-name verilator'
  * }}}
  */
object GCDMain extends App {

/**
  * This provides a way to run the firrtl-interpreter REPL (or shell)
  * on the lowered firrtl generated by your circuit. You will be placed
  * in an interactive shell. This can be very helpful as a debugging
  * technique. Type help to see a list of commands.
  *
  * To run from sbt
  * {{{
  * test:runMain gcd.GCDRepl
  * }}}
  * To run from sbt and see the half a zillion options try
  * {{{
  * test:runMain gcd.GCDRepl --help
  * }}}
  */
object GCDRepl extends App {
  iotesters.Driver.executeFirrtlRepl(args, () => new GCD)
}

  iotesters.Driver.execute(args, () => new GCD) {
    c => new GCDUnitTester(c)
  }


// See README.md for license details.

package gcd

import java.io.File

import chisel3.iotesters
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}

class GCDUnitTester(c: GCD) extends PeekPokeTester(c) {
  /**
    * compute the gcd and the number of steps it should take to do it
    *
    * @param a positive integer
    * @param b positive integer
    * @return the GCD of a and b
    */
  def computeGcd(a: Int, b: Int): (Int, Int) = {
    var x = a
    var y = b
    var depth = 1
    while(y > 0 ) {
      if (x > y) {
        x -= y
      }
      else {
        y -= x
      }
      depth += 1
    }
    (x, depth)
  }

  private val gcd = c

  for(i <- 1 to 40 by 3) {
    for (j <- 1 to 40 by 7) {
      poke(gcd.io.value1, i)
      poke(gcd.io.value2, j)
      poke(gcd.io.loadingValues, 1)
      step(1)
      poke(gcd.io.loadingValues, 0)

      val (expected_gcd, steps) = computeGcd(i, j)

      step(steps - 1) // -1 is because we step(1) already to toggle the enable
      expect(gcd.io.outputGCD, expected_gcd)
      expect(gcd.io.outputValid, 1)
    }
  }
}

/**
  * This is a trivial example of how to run this Specification
  * From within sbt use:
  * {{{
  * testOnly gcd.GCDTester
  * }}}
  * From a terminal shell use:
  * {{{
  * sbt 'testOnly gcd.GCDTester'
  * }}}
  */
class GCDTester extends ChiselFlatSpec {

  }
  for ( backendName <- backendNames ) {
    "GCD" should s"calculate proper greatest common denominator (with $backendName)" in {
      Driver(() => new GCD, backendName) {
        c => new GCDUnitTester(c)
      } should be (true)
    }
  }

  "Basic test using Driver.execute" should "be used as an alternative way to run specification" in {
    iotesters.Driver.execute(Array(), () => new GCD) {
      c => new GCDUnitTester(c)
    } should be (true)
  }

  if(backendNames.contains("verilator")) {
    "using --backend-name verilator" should "be an alternative way to run using verilator" in {
      iotesters.Driver.execute(Array("--backend-name", "verilator"), () => new GCD) {
        c => new GCDUnitTester(c)
      } should be(true)
    }
  }

  "running with --is-verbose" should "show more about what's going on in your tester" in {
    iotesters.Driver.execute(Array("--is-verbose"), () => new GCD) {
      c => new GCDUnitTester(c)
    } should be(true)
  }

  /**
    * By default verilator backend produces vcd file, and firrtl and treadle backends do not.
    * Following examples show you how to turn on vcd for firrtl and treadle and how to turn it off for verilator
    */

  "running with --generate-vcd-output on" should "create a vcd file from your test" in {
    iotesters.Driver.execute(
      Array("--generate-vcd-output", "on", "--target-dir", "test_run_dir/make_a_vcd", "--top-name", "make_a_vcd"),
      () => new GCD
    ) {

      c => new GCDUnitTester(c)
    } should be(true)

    new File("test_run_dir/make_a_vcd/GCD.vcd").exists should be (true)
  }

  "running with --generate-vcd-output off" should "not create a vcd file from your test" in {
    iotesters.Driver.execute(
      Array("--generate-vcd-output", "off", "--target-dir", "test_run_dir/make_no_vcd", "--top-name", "make_no_vcd",
      "--backend-name", "verilator"),
      () => new GCD
    ) {

      c => new GCDUnitTester(c)
    } should be(true)

    new File("test_run_dir/make_no_vcd/make_a_vcd.vcd").exists should be (false)

  }

}

  private val backendNames = if(firrtl.FileUtils.isCommandAvailable(Seq("verilator", "--version"))) {
    Array("firrtl", "verilator")
  }


// See README.md for license details.

package gcd

import chisel3._
import chisel3.tester._
import org.scalatest.FreeSpec
import chisel3.experimental.BundleLiterals._

/**
  * This is a trivial example of how to run this Specification
  * From within sbt use:
  * {{{
  * testOnly gcd.GcdDecoupledTester
  * }}}
  * From a terminal shell use:
  * {{{
  * sbt 'testOnly gcd.GcdDecoupledTester'
  * }}}
  */
//scalastyle:off magic.number
class GcdTesters2 extends FreeSpec with ChiselScalatestTester {

  "Gcd should calculate proper greatest common denominator" in {
    test(new DecoupledGcd(16)) { dut =>
      dut.input.initSource()

      val testValues = for { x <- 0 to 10; y <- 0 to 10} yield (x, y)
      val inputSeq = testValues.map { case (x, y) => (new GcdInputBundle(16)).Lit(_.value1 -> x.U, _.value2 -> y.U) }
      val resultSeq = testValues.map { case (x, y) =>
        (new GcdOutputBundle(16)).Lit(_.value1 -> x.U, _.value2 -> y.U, _.gcd -> BigInt(x).gcd(BigInt(y)).U)
      }

      fork {
        // push inputs into the calculator, stall for 11 cycles one third of the way
        val (seq1, seq2) = inputSeq.splitAt(resultSeq.length / 3)
        dut.input.enqueueSeq(seq1)
        dut.clock.step(11)
        dut.input.enqueueSeq(seq2)
      }.fork {
        // retrieve computations from the calculator, stall for 10 cycles one half of the way
        val (seq1, seq2) = resultSeq.splitAt(resultSeq.length / 2)
        dut.output.expectDequeueSeq(seq1)
        dut.clock.step(10)
        dut.output.expectDequeueSeq(seq2)
      }.join()

    }
  }
}

      dut.input.setSourceClock(dut.clock)
      dut.output.initSink()


package router.pipeline

import org.pcap4j.core._;
import java.io.File
import java.io.EOFException
import java.nio.ByteBuffer
import chisel3._
import chisel3.tester._
import org.scalatest.FreeSpec
import chisel3.util.Decoupled
import scala.collection.mutable.ArrayBuffer
import router._

class RouterTester extends PipelineTester {
  "Router generate output" in {
    test(new Router()) { dut =>
      initAndInput(dut, "src/test/resources/test_in.pcap")
        .fork {
          dumpOutput(dut, "src/test/resources/test_out.pcap")
        }
        .join()
    }
  }
}

class LoopbackTester extends PipelineTester {
  "Loopback generate output" in {
    test(new LoopbackPipeline()) { dut =>
      initAndInput(dut, "src/test/resources/test_in.pcap")
        .fork {
          dumpOutput(dut, "src/test/resources/loopback_out.pcap")
        }
        .join()
    }
  }
}

class ArpPipelineTester extends PipelineTester {
  "ArpPipeline should works" in {
    test(new ArpPipeline()) { dut =>
      initAndInput(dut, "src/test/resources/arp_in.pcap")
        // .fork {
        //   dumpOutput(dut, "src/test/resources/arp_out.pcap")
        // }
        .fork {
          val output = loadAxisFromPcap("src/test/resources/arp_ans.pcap")
          dut.io.out.expectDequeueSeq(output)
        }
        .join()
    }
  }
}

class NopTester extends PipelineTester {
  "Nop should works" in {
    test(new Pipeline {
      io.out <> io.in
    }) { dut =>
      initAndInput(dut, "src/test/resources/test_in.pcap")
        .fork {
          val output = loadAxisFromPcap("src/test/resources/test_in.pcap")
          dut.io.out.expectDequeueSeq(output)
        }
        .join()
    }
  }
}

class PipelineTester extends FreeSpec with ChiselScalatestTester {

  def initAndInput(dut: Pipeline, filePath: String) = {
    dut.io.in.initSource().setSourceClock(dut.clock)
    dut.io.out.initSink().setSinkClock(dut.clock)
    for ((iface, i) <- dut.io.config.iface.zipWithIndex) {
      iface.ipv4.poke(Ipv4Addr(s"10.0.$i.1"))
      iface.mask.poke(Ipv4Addr("255.255.255.0"))
      iface.mac.poke(MacAddr(s"RJGG_$i".map(_.toByte).toArray))
    }
    fork {
      val input = loadAxisFromPcap(filePath)
      for (elt <- input) {
        dut.io.in.enqueue(elt)
        dut.clock.step(5)
      }
    }
  }

  def dumpOutput(dut: Pipeline, filePath: String) = {
    val buf = ArrayBuffer[Byte]()
    val packets = ArrayBuffer[(Int, Array[Byte])]()
    var end = false
    while (!end) {
      dut.io.out.ready.poke(true.B)
      fork
        .withRegion(Monitor) {
          // wait for valid
          var count = 0
          val timeout = 100
          while (
            dut.io.out.valid.peek().litToBoolean == false && count < timeout
          ) {
            dut.clock.step(1)
            count += 1
          }
          if (count == timeout) {
            end = true
          } else {
            val bits = dut.io.out.bits.peek()
            val id = bits.id.litValue.toInt
            val last = bits.last.litToBoolean
            val data = bits.litToBytes
            buf ++= data
            if (last) {
              // Console.println(id, buf.map { b => f"$b%02x" }.mkString(" "))
              packets += ((id, buf.toArray[Byte]))
              buf.clear()
            }
          }
        }
        .joinAndStep(dut.clock)
      dut.io.out.ready.poke(false.B)
      dut.clock.step(5)
    }
    storePackets(filePath, packets)
  }

  def loadAxisFromPcap(filePath: String): Seq[AXIStreamData] =
    loadPackets(filePath).flatMap {
      case (id, data) => AXIStreamData.fromPacket(id, data, 8 * 48)
    }

  def loadPackets(filePath: String): Seq[(Int, Array[Byte])] = {
    val handle = Pcaps.openOffline(filePath)
    val packets = ArrayBuffer[(Int, Array[Byte])]()
    try {
      while (true) {
        val packet = handle.getNextRawPacketEx()
        // extract Vlan ID
        val id = ByteBuffer
          .allocate(2)
          .put(packet.slice(14, 16))
          .flip
          .getShort() - 1000
        val data = packet.slice(0, 12) ++ packet.splitAt(16)._2
        packets += ((id, data))
      }
    } catch {

    }
    handle.close()
    packets
  }

  def storePackets(filePath: String, packets: Seq[(Int, Array[Byte])]) {
    val handle =
      Pcaps.openOffline("src/test/resources/test_in.pcap").dumpOpen(filePath)
    for ((id, data) <- packets) {
      val (head, tail) = data.splitAt(12)
      val buf = ByteBuffer
        .allocate(data.length + 4)
        .put(head)
        .putShort(0x8100.toShort)
        .putShort((1000 + id).toShort)
        .put(tail)
      handle.dumpRaw(buf.array())
    }
    handle.close()
  }
}


package router.table

import chisel3._
import chisel3.tester._
import org.scalatest._
import router._

class ArpCacheTester extends FlatSpec with ChiselScalatestTester {
  behavior of "ArpCache"

  it should "works" in {
    test(new ArpCache(2)) { dut =>
      val query = dut.io.query
      val modify = dut.io.modify

      def insert(key: Ipv4Addr, value: MacAddr) = {
        modify.op.poke(ArpOp.Insert)
        modify.ipv4.poke(key)
        modify.mac.poke(value)
        dut.clock.step(1)
      }

      def remove(key: Ipv4Addr) = {
        modify.op.poke(ArpOp.Remove)
        modify.ipv4.poke(key)
        dut.clock.step(1)
      }

      def clear() = {
        modify.op.poke(ArpOp.Clear)
        dut.clock.step(1)
      }

      def check(key: Ipv4Addr, value: Option[MacAddr]) = {
        query.ipv4.poke(key)
        query.mac.valid.expect(value.isDefined.B)
        if (value.isDefined) {
          query.mac.bits.expect(value.get)
        }
      }

      // Insert
      insert(Ipv4Addr("192.168.1.1"), MacAddr("00:00:00:00:00:01"))
      check(Ipv4Addr("192.168.1.1"), Some(MacAddr("00:00:00:00:00:01")))

      // Insert override value
      insert(Ipv4Addr("192.168.1.1"), MacAddr("ff:ff:ff:ff:ff:ff"))
      check(Ipv4Addr("192.168.1.1"), Some(MacAddr("ff:ff:ff:ff:ff:ff")))

      // Insert override key
      insert(Ipv4Addr("192.168.0.0"), MacAddr("00:00:00:00:00:00"))


/*
 * MIT License
 *
 * Copyright (c) 2025 loykylewong (loywong@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import chisel3.RawModule
import circt.stage.{ChiselStage, FirtoolOption}
import chisel3.stage.ChiselGeneratorAnnotation

import java.nio.file.{Files, Paths}
import scala.util.matching.Regex
import loywong._
import loywong.examples._

object gen_verilog extends App {

    /**
     * Top level module(s) specified here.
     */
    val genModules = Array[() => RawModule](
        () => new test_fp,
        () => new encoder_example(),
        () => new decoder_example(),
        () => new shift_reg_example,
        () => new delay_chain_example,
        () => new counter_example(),
        () => new counter_oneshot_example(),
        () => new counter_max_example(),
        () => new count_down_example,
        () => new counterHrMinSec,
        () => new accumulator_example,
        () => new spram_example,
        () => new spram_with_init_example,
        () => new spramrf_example,
        () => new spramrf_with_init_example,
        () => new spramwf_example,
        () => new spramra_example,
        () => new sdpram_example,
        () => new sdpramrf_example,
        () => new sdpramwf_example,
        () => new sdpramra_example,
        () => new dpram_example,
        () => new sdcram_example,
        () => new dcram_example,
        () => new scfifo2_example,
        () => new anti_metastable_example,
        () => new edge2en_example,
        () => new ccd_trigger_example,
        () => new ccd_counter_example,
        () => new dcfifo_example,
        () => new str_frs_example,
        () => new str_birs_example,
        () => new hscomb1_example,
        () => new hscomb2_example,
        () => new str_dwc_example,
        () => new str_keep_remover_example,
        () => new str_dcfifo_example,
        () => new str_stage_example_0(),
        () => new str_stage_example_1(),
        () => new str_stage_example_2(),
        () => new Axi4LiteToLocalMemoryMapWrapper(6, 4),
        () => new cordic_example
    )

    /**
     * Directory where generated verilog/systemverilog file placed,
     * default using value predefined in build.sbt.
     */
    val targetDir: String = ProjectInfo.BuildInfo.targetVerilogDir

    /**
     * Whether generate "old school" verilog for compatible with old hdl
     * synthesis tools. It's useful for directly "Add Module ..." in Vivado
     * block design.
     */
    val genOldSchoolVerilog: Boolean = true

    /**
     * Whether generate split files for each module.
     */
    val genSplitFiles: Boolean = false

    println("================================")
    println(s"Project Info: ${ProjectInfo.BuildInfo.toString}")
    println("--------------------------------")
    println("Pre-processing...")

    private val chiselStageArgs = Array(
        "--target-dir", targetDir,
        "--target", if (genOldSchoolVerilog) "verilog" else "systemverilog",

    )

    private val chiselStageAnnotations =
        if (genOldSchoolVerilog)
            Seq(
                // ChiselGeneratorAnnotation(genModule),
                FirtoolOption("-strip-debug-info"),
                FirtoolOption("--disable-all-randomization"),
                FirtoolOption("--lowering-options=" +
                        "noAlwaysComb," +
                        "disallowLocalVariables," +
                        "disallowPortDeclSharing," +
                        "disallowPackedArrays," +
                        "disallowPackedStructAssignments"),
                FirtoolOption("--verilog"),
                FirtoolOption("-format=fir")
            )
        else
            Seq(
                // ChiselGeneratorAnnotation(genModule),
                FirtoolOption("-strip-debug-info"),
                FirtoolOption("--disable-all-randomization"),
                FirtoolOption("--lowering-options=" +
                        "disallowLocalVariables," +
                        "disallowPortDeclSharing," +
                        "explicitBitcast"),
                FirtoolOption("-format=fir")
            )

    println("Done.")
    println("--------------------------------")
    println("Emitting Verilog...")

    genModules.zipWithIndex.foreach {
        case (genModule, i) => {
            (new ChiselStage).execute(
                chiselStageArgs,
                Seq(ChiselGeneratorAnnotation(genModule)) ++ chiselStageAnnotations
            )
            println(s"${1+i}/${genModules.length} done.")
        }
    }

    println("Done.")
    println("--------------------------------")
    println("Post-processing...")

    /**
     * In which file(s), chisel style AXI4, AXI4-Lite and AXI4-Stream IO ports
     * name will be renamed to AMBA & Vivado IP conventions.
     */
    val filesToBeRenamed = List(
        "str_frs_example",
        "Axi4LiteToLocalMemoryMapWrapper_64x32"
    ).map(targetDir + "/" + _)
    RenamePorts2AmbaConventions(false, filesToBeRenamed: _*)

    println("Done.")
    println("================================")
}

object RenamePorts2AmbaConventions {
    /**
     * rename ports to AMBA conventions,
     * see `RenamePorts2AmbaConventions.md` for details.
     * @param deleteOriginal whether delete original file(s)
     * @param files          in these files (file names with path without
     *                       extension), all eligible identifiers will be
     *                       renamed
     */
    def apply(deleteOriginal: Boolean, files: String*): Unit = {

        val axi4sPattern: Regex = """([sm][^\W_]*(_[^\W_]+)*_axis[^\W_]*(_[^\W_]+)*)_(t?(valid|ready)|[^\W_]+_t?(last|data|user|id|keep|strb))\b""".r
        val axi4sReplace: String = "$1_t$5$6"
        val axi4Pattern: Regex = """([sm][^\W_]*(_[^\W_]+)*_(axi|axi[^\W_s][^\W_]*)(_[^\W_]+)*)_(aw|w|b|ar|r)(_[^\W_]+)?_t?(ready|valid|addr|prot|data|strb|resp|id|len|size|burst|lock|cache|qos|region|user)\b""".r
        val axi4Replace: String = "$1_$5$7"
        files.foreach {
            file => {
                val ext: String =
                    if (Files.exists(Paths.get(file + ".v"))) {
                        ".v"
                    }
                    else if (Files.exists(Paths.get(file + ".sv"))) {
                        ".sv"
                    }
                    else {
                        ""
                    }
                if (ext.isEmpty) {
                    println(s"\u001b[33m[warning]\u001b[0m in Rename2AmbaConventions: file \"${file}.v\" or \"${file}.sv\" not found.")
                }
                else {
                    var source = Files.readString(Paths.get(file + ext))
                    source = axi4sPattern.replaceAllIn(source, axi4sReplace)
                    source = axi4Pattern.replaceAllIn(source, axi4Replace)
                    Files.writeString(Paths.get(file + "_renamed" + ext), source)
                    println(s"\u001b[32m[info]\u001b[0m File \"${file}_renamed${ext}\" created.")
                    if (deleteOriginal) {
                        Files.delete(Paths.get(file + ext))
                        println(s"\u001b[32m[info]\u001b[0m File \"${file}${ext}\" deleted.")
                    }
                }
            }
        }
    }
}

if (genSplitFiles) "--split-verilog" else ""


/*
 * MIT License
 *
 * Copyright (c) 2025 loykylewong (loywong@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package loywong

import chisel3._
import chisel3.util._

class Accumulator(modu: Int) extends Module {
    require(modu > 1)
    val dw = log2Up(modu)
    val idw = dw + 2
    val io = IO(new Bundle {
        val en = Input(Bool())
        val step = Input(SInt(dw.W))
        val accu = Output(UInt(dw.W))
        val wrapUp = Output(Bool())
        val wrapDown = Output(Bool())
    })

    val accu = RegInit(0.U(dw.W))
    val nextRaw = Wire(SInt(idw.W))
    val wrapUp = WireDefault(nextRaw >= modu.S)
    val wrapDown = WireDefault(nextRaw < 0.S)
    val nextWrap = Wire(UInt(dw.W))
    nextRaw := (0.U(1.W) ## accu).asSInt +& io.step
    nextWrap := MuxCase(nextRaw(dw - 1, 0), Seq(
        wrapUp -> (nextRaw - modu.S)(dw - 1, 0),
        wrapDown -> (nextRaw + modu.S)(dw - 1, 0)
    ))

    io.wrapDown := wrapDown && io.en
}

package examples {
    class accumulator_example extends Module {
        val modu = 100
        val io = IO(new Bundle {
            val en = Input(Bool())
            val step = Input(SInt(7.W))
            val accu = Output(UInt(7.W))
            val wrapUp = Output(Bool())
            val wrapDown = Output(Bool())
        })
        val theAccu = Module(new Accumulator(modu))
        io <> theAccu.io

    }
}

    when(io.en) {
        accu := nextWrap
    }


/*
 * MIT License
 *
 * Copyright (c) 2025 loykylewong (loywong@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package loywong

import chisel3._
import chisel3.util._

/**
 * Local Memory Map I.F. with:
 *  - address  : the register address, not the byte address
 *  - write    : write enable signal for issuing a write transaction
 *  - writeData: data to be written to the address when write asserted
 *  - writeByteEn : byte enable for writeData in a write transaction
 *  - read      : read enable signal for issuing a read transaction
 *  - readData  : data read from the address when read asserted
 * @param addrWidth width of the address
 * @param dataBytes number of bytes of data
 * @note
 *  - In write transaction, `address`, `writeData` and `writeByteEn` must be
 *    active in the same cycle when write asserted, that is:
 *    - Write Wait = 0
 *    - Write Latency = 0
 *  - In read transaction, `address` must be active in the same cycle when read
 *    asserted, and readData must be valid in the very next cycle, that is:
 *    - Read Wait = 0
 *    - Read Latency = 1
 */
class LocalMemoryMapIO(addrWidth: Int, dataBytes: Int = 4) extends Bundle {
    val address     = Output(UInt(addrWidth.W))
    val write       = Output(Bool())
    val writeData   = Output(UInt((dataBytes*8).W))
    val writeByteEn = Output(UInt(dataBytes.W))
    val read        = Output(Bool())
    val readData    = Input(UInt((dataBytes*8).W))
}

object LocalMemoryMap {
    /**
     * Create a instance of Local Memory Map Master IO
     * @param addrWidth Width of register word address
     * @param dataBytes Number of data bytes
     * @return          Instance created
     */
    def MasterIO(addrWidth: Int, dataBytes: Int = 4) = {
        new LocalMemoryMapIO(addrWidth, dataBytes)
    }

    /**
     * Create a instance of Local Memory Map Slave IO
     * @param addrWidth Width of register word address
     * @param dataBytes Number of data bytes
     * @return          Instance created
     */
    def SlaveIO(addrWidth: Int, dataBytes: Int = 4) = {
        Flipped(new LocalMemoryMapIO(addrWidth, dataBytes))
    }
}

class Axi4LiteIO(addrWidth: Int, dataBytes: Int = 4) extends Bundle {
    class AWBits extends Bundle {
        val addr = UInt(addrWidth.W)
        val prot = UInt(3.W)
    }
    class WBits extends Bundle {
        val data = UInt((dataBytes*8).W)
        val strb = UInt(dataBytes.W)
    }
    class BBits extends Bundle {
        val resp = UInt(2.W)
    }
    class ARBits extends Bundle {
        val addr = UInt(addrWidth.W)
        val prot = UInt(3.W)
    }
    class RBits extends Bundle {
        val data = UInt((dataBytes*8).W)
        val resp = UInt(2.W)
    }
    val aw = Irrevocable(new AWBits)
    val w  = Irrevocable(new WBits)
    val b  = Flipped(Irrevocable(new BBits))
    val ar = Irrevocable(new ARBits)
    val r  = Flipped(Irrevocable(new RBits))
}

object Axi4Lite {
    /**
     * Create a instance of AXI4-Lite Master IO
     * @param addrWidth Width of address (byte address)
     * @param dataBytes Number of data bytes
     * @return          Instance created
     */
    def MasterIO(addrWidth: Int, dataBytes: Int = 4)  = {
        new Axi4LiteIO(addrWidth, dataBytes)
    }
    /**
     * Create a instance of AXI4-Lite Slave IO
     * @param addrWidth Width of address (byte address)
     * @param dataBytes Number of data bytes
     * @return          Instance created
     */
    def SlaveIO(addrWidth: Int, dataBytes: Int = 4) = {
        Flipped(new Axi4LiteIO(addrWidth, dataBytes))
    }
}

/**
 * Represent a hardware AXI4-Lite Slave to local memory map master bridge
 * module.
 * @param regAddrWidth  Width of register word address (not the byte address)
 * @param regDataBytes  Number of bytes of tdata in AXI4-Lite and local memory
 *                      map I.F.
 */
class Axi4LiteToLocalMemoryMap(regAddrWidth: Int, regDataBytes: Int = 4) extends Module {
    require(regAddrWidth >= 0)
    require(isPow2(regDataBytes))

    override def desiredName: String = super.desiredName +

    val byteAddrExt = log2Ceil(regDataBytes)
    val byteAddrWidth = regAddrWidth + byteAddrExt

    val io = IO(new Bundle {
        val axi4l = Axi4Lite.SlaveIO(byteAddrWidth, regDataBytes)
        val lmm = LocalMemoryMap.MasterIO(regAddrWidth, regDataBytes)
    })

    val regs_wr = WireDefault(Bool(), io.axi4l.w.fire)
    val regs_rd = WireDefault(Bool(), io.axi4l.ar.fire)

    // ==== aw channel ====
    io.axi4l.aw.ready := true.B // always ready
    val waddr_reg = RegInit(0.U(regAddrWidth.W))
    when(io.axi4l.aw.valid) {
        waddr_reg := io.axi4l.aw.bits.addr >> byteAddrExt
    }
    // ==== w channel ====
    val wready = RegInit(false.B)
    when(io.axi4l.aw.valid) {
        wready := true.B
    }.elsewhen(io.axi4l.w.fire) {
        wready := false.B
    }
    io.axi4l.w.ready := wready
    // ==== b channel ====
    io.axi4l.b.bits.resp := "b00".U(2.W) // always OK
    val bvalid = RegInit(false.B)
    when(io.axi4l.w.fire) {
        bvalid := true.B
    }.elsewhen(io.axi4l.b.fire) {
        bvalid := false.B
    }
    io.axi4l.b.valid := bvalid
    // ==== ar channel ====
    val raddr_reg = RegInit(0.U(regAddrWidth.W))
    when(io.axi4l.ar.valid) {
        raddr_reg := io.axi4l.ar.bits.addr >> byteAddrExt
    }
    val arready = RegInit(false.B)
    when(io.axi4l.ar.valid && !io.axi4l.ar.ready) {
        arready := true.B
    }.elsewhen(io.axi4l.ar.fire) {
        arready := false.B
    }
    io.axi4l.ar.ready := arready
    // ==== r channel ====
    io.axi4l.r.bits.resp := 0.U(2.W) // always OK
    val rvalid = RegInit(false.B)
    when(io.axi4l.ar.fire) {
        rvalid := true.B
    }.elsewhen(io.axi4l.r.fire) {
        rvalid := false.B
    }
    io.axi4l.r.valid := rvalid
    // ==== lmm ====
    io.lmm.address := Mux(regs_wr, waddr_reg, raddr_reg)
    io.lmm.write := regs_wr
    io.lmm.writeData := io.axi4l.w.bits.data
    io.lmm.writeByteEn := io.axi4l.w.bits.strb
    io.lmm.read := regs_rd
    io.axi4l.r.bits.data := io.lmm.readData
}

class Axi4LiteToLocalMemoryMapWrapper(regAddrWidth: Int, regDataBytes: Int = 4) extends RawModule {
    require(regAddrWidth >= 0)
    require(isPow2(regDataBytes))

    override def desiredName: String = super.desiredName +
            s"_${1 << regAddrWidth}x${regDataBytes * 8}"

    val byteAddrExt = log2Ceil(regDataBytes)
    val byteAddrWidth = regAddrWidth + byteAddrExt

    val aclk = IO(Input(Clock()))
    val areset_n = IO(Input(Bool()))
    val axi4l = IO(Axi4Lite.SlaveIO(byteAddrWidth, regDataBytes)).suggestName("s_axi4l")
    val lmm = IO(LocalMemoryMap.MasterIO(regAddrWidth, regDataBytes))

    val axi4l_to_lmm = withClockAndReset(aclk, !areset_n) {
        Module(new Axi4LiteToLocalMemoryMap(regAddrWidth, regDataBytes))
    }

    axi4l <> axi4l_to_lmm.io.axi4l
    lmm <> axi4l_to_lmm.io.lmm
}


/*
 * MIT License
 *
 * Copyright (c) 2025 loykylewong (loywong@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package loywong

import chisel3._
import chisel3.util._
import loywong.util._

object CordicCoordinates extends Enumeration {
    val Circular = Value(1)
    val Linear = Value(0)
    val Hyperbolic = Value(-1)
}

object CordicMode extends Enumeration {
    val Rotation = Value(0)
    val Vectoring = Value(1)
}

class CordicIOStrBits(xyDW: Int, zDW: Int) extends StrBits {
    val x = SInt(xyDW.W)
    val y = SInt(xyDW.W)
    val z = SInt(zDW.W)
}

/**
 * Represent a hardware stream stage implemented one iteration step of CORDIC.
 * @param gen           generator of CORDIC IO bits, must be inherited from
 *                      `CordicIOStrBits`
 * @param stgIdx        index of iteration step
 * @param zFW           fraction width of `z` (angle)
 * @param coordinates   coordinate system
 * @param mode          rotation or vectoring mode
 * @tparam T            type of CORDIC IO bits
 * @note                Do NOT use this class.
 */
class CordicStage[T <: CordicIOStrBits](gen: T, stgIdx: Int, zFW: Int,
                                        coordinates: CordicCoordinates.Value,
                                        mode: CordicMode.Value)
  extends StrActiveStage(gen, gen)() {

    val xyDW = gen.x.getWidth
    val zDW = gen.z.getWidth

    coordinates match {
        case CordicCoordinates.Circular =>
            require(stgIdx >= 0)
        case CordicCoordinates.Hyperbolic =>
            require(stgIdx >= 1)
        case _ => { }
    }

    def atanh(x: Double): Double = {
        if (x.abs >= 1) Double.NaN
        else 0.5 * math.log((1 + x) / (1 - x))
    }

    // ---- parameters ----
    val deltaZDouble: Double = coordinates match {
        // for circular, map angle from [-pi, pi) to [-1, 1) (Qx.FW)
        case CordicCoordinates.Circular => math.atan(math.pow(2.0, -stgIdx)) / math.Pi
        case CordicCoordinates.Linear => math.pow(2.0, -stgIdx)
        case CordicCoordinates.Hyperbolic => atanh(math.pow(2.0, -stgIdx))
    }
    val deltaZ = Math.round(deltaZDouble * math.pow(2.0, zFW)).asSInt

    val sigma: Bool = mode match {
        case CordicMode.Rotation => !io.us.bits.z.signBit
        case CordicMode.Vectoring => io.us.bits.y.signBit
    }

    // ==== override `ds_bits_next` in `StrActiveStage` ====
    private def shiftRight(x: SInt, n: Int): Bits = {
        if (n >= 0) x >> n
        else x << -n
    }
    // ---- x ----
    coordinates match {
        case CordicCoordinates.Linear =>
            ds_bits_next.x := io.us.bits.x
        case CordicCoordinates.Circular =>
            when(sigma) {
                ds_bits_next.x := io.us.bits.x - shiftRight(io.us.bits.y, stgIdx).asSInt
            }.otherwise {
                ds_bits_next.x := io.us.bits.x + shiftRight(io.us.bits.y, stgIdx).asSInt
            }
        case CordicCoordinates.Hyperbolic =>
            when(sigma) {
                ds_bits_next.x := io.us.bits.x + shiftRight(io.us.bits.y, stgIdx).asSInt
            }.otherwise {
                ds_bits_next.x := io.us.bits.x - shiftRight(io.us.bits.y, stgIdx).asSInt
            }
    }
    // ---- y ----
    when(sigma) {
        ds_bits_next.y := io.us.bits.y + shiftRight(io.us.bits.x, stgIdx).asSInt
    }.otherwise {
        ds_bits_next.y := io.us.bits.y - shiftRight(io.us.bits.x, stgIdx).asSInt
    }
    // ---- z ----
    when(sigma) {
        ds_bits_next.z := io.us.bits.z - deltaZ
    }.otherwise {
        ds_bits_next.z := io.us.bits.z + deltaZ
    }
}

class CordicCircularQuadrantTrans[T <: CordicIOStrBits](gen: T, mode: CordicMode.Value)
        extends StrActiveStage(gen, gen)() {

    val xyDW = gen.x.getWidth
    val zDW = gen.z.getWidth

    val zInQuad2or3 = io.us.bits.z(zDW - 1, zDW - 2).xorR
    val xInQuad2or3 = io.us.bits.x(xyDW - 1)

    mode match {
        case CordicMode.Rotation => {
            when(zInQuad2or3) {
                ds_bits_next.x := -io.us.bits.x
                ds_bits_next.y := -io.us.bits.y
                ds_bits_next.z := io.us.bits.z ^ (1.U << (zDW - 1)).asSInt
            }.otherwise {
                ds_bits_next.x := io.us.bits.x
                ds_bits_next.y := io.us.bits.y
                ds_bits_next.z := io.us.bits.z
            }
        }
        case CordicMode.Vectoring => {
            when(xInQuad2or3) {
                ds_bits_next.x := -io.us.bits.x
                ds_bits_next.y := -io.us.bits.y
                ds_bits_next.z := io.us.bits.z ^ (1.U << (zDW - 1)).asSInt
            }.otherwise {
                ds_bits_next.x := io.us.bits.x
                ds_bits_next.y := io.us.bits.y
                ds_bits_next.z := io.us.bits.z
            }
        }
    }
}

class CordicLinearVectoringQuadrantTrans[T <: CordicIOStrBits](gen: T)
        extends StrActiveStage(gen, gen)() {

    val xyDW = gen.x.getWidth

    val xInQuad2or3 = io.us.bits.x(xyDW - 1)

    when(xInQuad2or3) {
        ds_bits_next.x := -io.us.bits.x
        ds_bits_next.y := -io.us.bits.y
        ds_bits_next.z := io.us.bits.z
    }.otherwise {
        ds_bits_next.x := io.us.bits.x
        ds_bits_next.y := io.us.bits.y
        ds_bits_next.z := io.us.bits.z
    }
}

class CordicHyperbolicVectoringQuadrantTrans[T <: CordicIOStrBits](gen: T)
        extends StrActiveStage(gen, gen)() {

    val xyDW = gen.x.getWidth

    val xInQuad2or3 = io.us.bits.x(xyDW - 1)

    when(xInQuad2or3) {
        ds_bits_next.x := -io.us.bits.x
        ds_bits_next.y := -io.us.bits.y
        ds_bits_next.z := io.us.bits.z
    }.otherwise {
        ds_bits_next.x := io.us.bits.x
        ds_bits_next.y := io.us.bits.y
        ds_bits_next.z := io.us.bits.z
    }
}

/**
 * Represent a hardware module of CORDIC algorithm
 *
 * @param gen           generator of generator of CORDIC IO bits, must be inherited from
 *                      `CordicIOStrBits`
 * @param nStages       number of iterations (pipe stages)
 * @param xyFW          fraction width of x and y
 * @param zFW           fraction width of z (angle)
 * @param coordinates   coordinate system
 * @param mode          rotation or vectoring mode
 * @param linStgStart   stage index start for linear coordinates,
 *                       - in Rotation mode, range of abs(z) < 2^(1 - linStgStart)^,
 *                       - in Vectoring mode, range of abs(y/x) < 2^(1 - linStgStart)^,
 *                       - x & y widths must make range of abs(x or y) covers
 *                       max{x_in, y_in} * 2^-linStgStart^, x_out, y_out}.
 * @tparam T            type of CORDIC IO bits
 * @note
 * - it's the user's responsibility to make sure the data width and fraction
 *   width match the CORDIC's input and output value domain.
 */
class Cordic[T <: CordicIOStrBits](gen: T, nStages: Int, xyFW: Int, zFW: Int,
                                   coordinates: CordicCoordinates.Value,
                                   mode: CordicMode.Value, linStgStart: Int = 0
               ) extends Module {

    val xyDW = gen.x.getWidth
    val zDW = gen.z.getWidth

    val nIterStages = nStages
    val nTotalStages = nIterStages + { coordinates match {
        case CordicCoordinates.Circular => 2    // quadTrans & scaleComp
        case CordicCoordinates.Hyperbolic => mode match {
            case CordicMode.Rotation => 1       // scaleComp
            case CordicMode.Vectoring => 2      // quadTrans & scaleComp
        }
        case CordicCoordinates.Linear => mode match {
            case CordicMode.Rotation => 0
            case CordicMode.Vectoring => 1      // quadTrans
        }
    }}

    if (coordinates == CordicCoordinates.Circular)
        require(zDW == zFW + 1)
    else if (coordinates == CordicCoordinates.Linear)
        require(linStgStart < zDW - zFW - 1)

    private def hyperbolicIter(i: Int): Int = {
        if (i <= 4) i // repeat 4
        else if (i <= 14) i - 1 // repeat 13
        else i - 2
    }

    val stageIdxSeq = coordinates match {
        case CordicCoordinates.Circular =>
            (0 until nStages).toList
        case CordicCoordinates.Hyperbolic =>
            (1 to nStages).map(i => hyperbolicIter(i))
        case CordicCoordinates.Linear =>
            (linStgStart until linStgStart + nStages).toList
    }

    val scale = coordinates match {
        case CordicCoordinates.Circular =>
            stageIdxSeq.map(i => math.sqrt(1.0 + math.pow(2.0, -2.0 * i))).foldLeft(1.0)(_ * _)
        case CordicCoordinates.Hyperbolic =>
            stageIdxSeq.map(i => math.sqrt(1.0 - math.pow(2.0, -2.0 * i))).foldLeft(1.0)(_ * _)
        case CordicCoordinates.Linear => 1.0
    }

    val io = IO(new Bundle {
        val us = Flipped(Irrevocable(gen))
        val ds = Irrevocable(gen)
    })

    class ScaleCompStage extends StrActiveStage(gen, gen)() {
        val scaleComp =
            if (coordinates == CordicCoordinates.Circular)
                (1.0 / scale).toFixPoint(xyDW.W, xyFW)
            else
                (1.0 / scale).toFixPoint((xyDW + 1).W, xyFW)
        ds_bits_next.x := (this.io.us.bits.x.asFixPoint(xyFW) * scaleComp).truncate(xyFW).getBits
        ds_bits_next.y := (this.io.us.bits.y.asFixPoint(xyFW) * scaleComp).truncate(xyFW).getBits
    }

    val scaleCompStage =
        if(coordinates != CordicCoordinates.Linear)
            Some(Module(new ScaleCompStage))
        else
            None

    val cordicStages = stageIdxSeq.map(s => {
        Module(new CordicStage(gen, s, zFW, coordinates, mode))
    })

    for (i <- 1 until cordicStages.length) {
        cordicStages(i - 1).io.ds <> cordicStages(i).io.us
    }

    coordinates match {
        case CordicCoordinates.Circular => {
            val quadTrans = Module(new CordicCircularQuadrantTrans(gen, mode))
            io.us <> quadTrans.io.us
            quadTrans.io.ds <> scaleCompStage.get.io.us
            scaleCompStage.get.io.ds <> cordicStages.head.io.us
            cordicStages.last.io.ds <> io.ds
        }
        case CordicCoordinates.Linear => {
            mode match {
                case CordicMode.Rotation => {
                    io.us <> cordicStages.head.io.us
                }
                case CordicMode.Vectoring => {
                    val quadTrans = Module(new CordicLinearVectoringQuadrantTrans(gen))
                    io.us <> quadTrans.io.us
                    quadTrans.io.ds <> cordicStages.head.io.us
                }
            }
            cordicStages.last.io.ds <> io.ds
        }
        case CordicCoordinates.Hyperbolic => {
            mode match {
                case CordicMode.Rotation => {
                    io.us <> cordicStages.head.io.us
                }
                case CordicMode.Vectoring => {
                    val quadTrans = Module(new CordicHyperbolicVectoringQuadrantTrans(gen))
                    io.us <> quadTrans.io.us
                    quadTrans.io.ds <> cordicStages.head.io.us
                }
            }
            cordicStages.last.io.ds <> scaleCompStage.get.io.us
            scaleCompStage.get.io.ds <> io.ds
        }

    }
}

package examples {
    class cordic_example extends Module {
        val xyDW = 12
        val xyFW = 10
        val zDW = 12
        val zFW = 11
        class CordicBits extends CordicIOStrBits(xyDW, zDW) {
            val sideband = UInt(8.W)
        }
        val io = IO(new Bundle {
            val us = Flipped(Irrevocable(new CordicBits))
            val ds = Irrevocable(new CordicBits)
        })
        val cordic = Module(new Cordic(new CordicBits,
            8, xyFW, zFW,
            CordicCoordinates.Circular,
            CordicMode.Rotation
        ))
        io.us <> cordic.io.us
        cordic.io.ds <> io.ds
    }
}


/*
 * MIT License
 *
 * Copyright (c) 2025 loykylewong (loywong@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package loywong

import chisel3._
import chisel3.util._

package examples {
    class counter_example(m: Int = 256) extends Module {
        val io = IO(new Bundle {
            val en = Input(Bool())
            val out = Output(UInt(log2Ceil(m).W))
            val co = Output(Bool())
        })
        // use chisel3.util.Counter !
        val (cnt, co) = Counter(io.en, m)
        io.out := cnt
        io.co := co
    }
}

object CounterOneShot {
    def apply(m:BigInt, en:Bool, continue:Bool = false.B): (UInt, Bool) = {
        val w = log2Ceil(m)
        val cnt = RegInit(0.U(w.W))
        val wrap = cnt === (m-1).U
        when(en) {
            when(wrap) {
                cnt := Mux(continue, 0.U(w.W), cnt)
            }.otherwise {
                cnt := cnt + 1.U(w.W)
            }
        }
        (cnt, en && wrap && continue)
    }
}

package examples {
    class counter_oneshot_example extends Module {
        val io = FlatIO(new Bundle {
            val en = Input(Bool())
            val dly_rst = Output(Bool())
        })
        val (cnt, _) = CounterOneShot(100, io.en)
        io.dly_rst := cnt === 99.U
    }
}

object CounterMax {
    def apply(width:Int, max:UInt, en:Bool): (UInt, Bool) = {
        val cnt = RegInit(0.U(width.W))
        val wrap = cnt === max

            }
        }
        (cnt, en && wrap)
    }
}

object CounterMaxOneShot {
    def apply(width:Int, max:UInt, en:Bool, continue:Bool = false.B): (UInt, Bool) = {
        val cnt = RegInit(0.U(width.W))
        val wrap = cnt === max
        when(en) {
            when(wrap) {
                cnt := Mux(continue, 0.U(width.W), cnt)
            }.otherwise {
                cnt := cnt + 1.U(width.W)
            }
        }
        (cnt, en && wrap && continue)
    }
}

package examples {
    class counter_max_example(width: Int = 8) extends Module {
        val io = IO(new Bundle {
            val max = Input(UInt(width.W))
            val en = Input(Bool())
            val out = Output(UInt(width.W))
            val co = Output(Bool())
        })
        val (out, co) = CounterMax(width, io.max, io.en)
        io.out := out
        io.co := co
    }

    class count_down_example extends Module {
        val io = FlatIO(new Bundle {
            val dly_rst = Output(Bool())
        })

        val en = Wire(Bool())
        val (cnt, wrap) = {
            Counter((99 to 0 by -1), en)
        }
        en := cnt =/= 0.U

        io.dly_rst := !en
    }

    class counterHrMinSec extends Module {
        val io = FlatIO(new Bundle {
            val en = Input(Bool())
            val sec = Output(UInt(6.W))
            val min = Output(UInt(6.W))
            val hr = Output(UInt(5.W))
            val en_day = Output(Bool())
        })
        val (_, en_sec) = Counter(io.en, 100e6.toInt)
        val (sec, en_min) = Counter(en_sec, 60)
        val (min, en_hr) = Counter(en_min, 60)
        val (hr, en_day) = Counter(en_hr, 24)
        io.sec := sec
        io.min := min
        io.hr := hr
        io.en_day := en_day

    }
}

        when(en) {
            when(wrap) {
                cnt := 0.U(width.W)


/*
 * MIT License
 *
 * Copyright (c) 2025 loykylewong (loywong@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package loywong

import chisel3._
import chisel3.util.RegEnable
// import chisel3.util._

/**
 * Utility class for anti-metastable by using 2 or more synchronize stages
 * (flip-flops).
 * @param gen       Generator of data
 * @param nStages   Number of synchronize stages
 * @param init      Function for generating reset value(s), default generate
 *                  all 0s, set to None for generating no reset logic.
 * @tparam T        Type of data
 * @note            You may prefer to use the homonymous factory object.
 * @example {{{
 *              val ams_1 = new AntiMetastable(UInt(8.W))(
 *                                      Some(()=>{ 255.U(8.W) }))
 *              // Or using the factory
 *              // val ams_1 = AntiMetastable(UInt(8.W))(
 *              //                     Some(()=>{ 255.U(8.W) }))
 *              io.some_out := ams_1.input(io.some_in)
 * }}}
 */
class AntiMetastable[T <: Data](gen: T, nStages: Int = 2)
                               (init: Option[()=>T] = Some(()=>0.U.asTypeOf(gen))) {
    require(nStages >=1, "In AntiMetastable, nStages must be >= 1.")
    val stgs = init match {
        case Some(f) => RegInit(VecInit(Seq.fill(nStages)(f())))
        case None    => Reg(Vec(nStages, gen))
    }

    /**
     * Insert synchronize stages between input and output.
     * @param data  Input data signal
     * @tparam T    Type of data
     * @return      Output after the synchronize stages
     */
    def input[T <: Data](data: T) = {
        stgs.zipWithIndex.foreach{ case(stg, i) => {
            if(i == 0) stg := data
            else       stg := stgs(i - 1)
        }}
        stgs(nStages - 1)
    }
}

object AntiMetastable {
    /**
     * Instantiate a utility class for anti-metastable by using 2 or more
     * synchronize stages (flip-flops).
     * @param gen       Generator of data
     * @param init      Initialization function for resetting data
     * @param nStages   Number of synchronize stages
     * @tparam T        Type of data
     * @note            You may prefer to use the homonymous factory object.
     * @example {{{
     *              val ams_1 = AntiMetastable(UInt(8.W))(
     *                                  Some(()=>{ 255.U(8.W) }))
     *              io.some_out := ams_1.input(io.some_in)
     * }}}
     */
    def apply[T <: Data](gen: T, nStages: Int = 2)
                        (init: Option[()=>T] = Some(()=>0.U.asTypeOf(gen))) = {
        new AntiMetastable(gen, nStages)(init)
    }

    /**
     * Utility method for inserting a 2-synchronize-stage anti-metastable circuit.
     * @param data  Input data signal
     * @param init  Function to generate init value, generate all 0s by
     *              default, generate no reset logic if set to None
     * @tparam T    Type of data
     * @return      Output data after 2 synchronize stages
     * @note        No reset in inserted stages.
     */
    def _2ff[T <: Data](data: T)
                       (init: Option[()=>T] = Some(()=>0.U.asTypeOf(data))) = {
        val stgs = init match {
            case None    => Reg(Vec(2, data.cloneType))
            case Some(f) => RegInit(VecInit(Seq.fill(2)(f())))
        }
        stgs(0) := data
        stgs(1) := stgs(0)
        stgs(1)
    }

    /**
     * Utility method for inserting a 3-synchronize-stage anti-metastable circuit.
     * @param data  Input data signal
     * @param init  Function to generate init value, generate all 0s by
     *              default, generate no reset logic if set to None
     * @tparam T    Type of data
     * @return      Output data after 3 synchronize stages
     * @note        No reset in inserted stages.
     */
    def _3ff[T <: Data](data: T)
                       (init: Option[()=>T] = Some(()=>0.U.asTypeOf(data))) = {
        val stgs = init match {
            case None    => Reg(Vec(3, data.cloneType))
            case Some(f) => RegInit(VecInit(Seq.fill(3)(f())))
        }
        stgs(0) := data
        stgs(1) := stgs(0)
        stgs(2) := stgs(1)
        stgs(2)
    }
}

object Edge2En {
    /**
     * Convert edges of "slow" signal to single cycle enable.
     * @param in        Input signal
     * @param initOnes  Reset synchronize stages to 1s if true
     * @param nStages   Number of synchronize stages
     * @return          (rising, falling, output)
     *                  rising : enable signal for rising edge
     *                  falling: enable signal for falling edge
     *                  output : input signal via synchronize stages
     * @example {{{
     *              (xx_rising, _, _) := Edge2En(xx)
     * }}}
     */
    def apply(in:Bool, initOnes:Boolean = false, nStages:Int = 2): (Bool, Bool, Bool) = {
        require(nStages >= 0, "In Edge2En, nStages must be >= 0.")
        val dly_init = if(initOnes) {
            // Fill(nStages + 1, 1.U(1.W))
            ~0.U((nStages + 1).W)
        } else {
            0.U((nStages + 1).W)
        }
        val dly = RegInit(dly_init.cloneType, dly_init)
        val rising = WireInit(false.B)
        val falling = WireInit(false.B)
        if(nStages == 0) {
            dly(0) := in
            rising  := "b01".U === dly(0) ## in
            falling := "b10".U === dly(0) ## in
        }
        else {
            dly := (dly << 1) | in.asUInt
            rising  := "b01".U === dly(nStages) ## dly(nStages - 1)
            falling := "b10".U === dly(nStages) ## dly(nStages - 1)
        }
        (rising, falling, dly(nStages))
    }
}

/*
/**
 * Utility class for cross clock domain trigger by toggle flip-flop and
 * synchronize stages.
 * @param fromClock Clock of the clock domain in which trigger signal sent
 * @param toClock   Clock of the clock domain in which trigger signal received
 * @param fromReset Reset in `fromClock` domain
 * @param toReset   Reset in `toClock` domain
 * @param nStages   Number of synchronize stages for anti-metastable between
 *                  clock domains
 * @note            You may prefer to use the homonymous factory object.
 * @example {{{
 *              val ccd_trig = new CcdTrigger(clk_a, clk_b)()
 *              withClock(clk_a) { when(xx_a) { ccd_trig.trigger() } }
 *              withClock(clk_b) { xx_b := ccd_trig.out() }
 *              // Or, use the factory
 *              // val ccd_trig = CcdTrigger(clk_a, clk_b)()
 *              // withClock(clk_a) { when(xx_a) { ccd_trig.trigger() } }
 *              // withClock(clk_b) { xx_b := ccd_trig.out() }
 * }}}
 */
class CcdTriggerInline(fromClock: Clock, toClock: Clock,
                 fromReset: Bool, toReset: Bool)
                (nStages: Int = 2) {
    require(nStages >=1, "nStages must be >= 1 in CcdTrigger")
    val toggle_a = withClockAndReset(fromClock, fromReset) {
        RegInit(false.B)
    }
    val toggle_b = withClockAndReset(toClock, toReset) {
        RegInit(0.U((nStages+1).W))
    }
    withClock(toClock) {
        toggle_b := (toggle_b << 1) | toggle_a
    }

    /**
     * Send a trigger signal, use in `fromClock` domain.
     */
    def trigger(en: Bool = true.B): Unit = {
        withClock(fromClock) {
            when(en) {
                toggle_a := !toggle_a
            }
        }
    }

    /**
     * Assigned to a wire for receiving trigger signal, use in `toClock`
     * domain. The wire assigned will be high for one clock period due to each
     * trigger signal sent from `fromClock`
     * domain.
     * @return Signal indicates trigger received.
     */
    def out(): Bool = {
        toggle_b(nStages, nStages - 1).xorR
    }
}

object CcdTriggerInline {
    /**
     * Create an instance of cross clock domain trigger.
     * @param fromClock Clock of the clock domain in which trigger signal sent
     * @param toClock   Clock of the clock domain in which trigger signal received
     * @param fromReset Reset in `fromClock` domain, optional, default false
     * @param toReset   Reset in `toClock` domain, optional, default false
     * @param nStages   Number of synchronize stages for anti-metastable between
     *                  clock domains
     * @return          Instance created
     * @note            You may prefer to use the homonymous factory object
     * @example {{{
     *              val ccd_trig = CcdTrigger(clk_a, clk_b)()
     *              withClock(clk_a) { when(xx_a) { ccd_trig.trigger() } }
     *              withClock(clk_b) { xx_b := ccd_trig.out() }
     * }}}
     */
    def apply(fromClock: Clock, toClock: Clock,
              fromReset: Bool, toReset: Bool)
             (nStages: Int = 2): CcdTrigger = {
        new CcdTriggerInline(fromClock, toClock, fromReset, toReset)(nStages)
    }
}
 */

/**
 * Represent a hardware Cross Clock Domain Trigger module.
 * @param nStages   Number of synchronize stages for anti-metastable
 */
class CcdTrigger(nStages: Int = 2) extends RawModule {
    val io = IO(new Bundle {
        val fromClock  = Input(Clock())
        val toClock    = Input(Clock())
        val fromReset  = Input(Bool())
        val toReset    = Input(Bool())
        val triggerIn  = Input(Bool())
        val triggerOut = Output(Bool())
    })
    require(nStages >=1, "In CcdTrigger, nStages must be >= 1.")

    val toggle_a = withClockAndReset(io.fromClock, io.fromReset) {
        RegInit(false.B)
    }
    val toggle_b = withClockAndReset(io.toClock, io.toReset) {
        RegInit(0.U((nStages+1).W))
    }
    withClock(io.toClock) {
        toggle_b := (toggle_b << 1) | toggle_a
    }

    withClock(io.fromClock) {
        when(io.triggerIn) {
            toggle_a := !toggle_a
        }
    }
    io.triggerOut := toggle_b(nStages, nStages - 1).xorR
}

object CcdTrigger {
    /**
     * Create an instance of class CcdTrigger and make connections.
     * @param fromClock Clock of clock domain in which trigger signal sent
     * @param toClock   Clock of clock domain in which trigger signal received
     * @param fromReset Reset of `fromClock` domain
     * @param toReset   Reset of `toClock` domain
     * @param triggerIn Trigger signal input
     * @param nStages   Number of synchronize stages for anti-metastable
     * @return          Trigger signal output
     * @example {{{
     *              val trig_out = CcdTrigger(clk_a, clk_b, rst_a, rst_b)(
     *                                        trig_in)
     * }}}
     */
    def apply(fromClock: Clock, toClock: Clock,
              fromReset: Bool, toReset: Bool)(triggerIn: Bool, nStages: Int = 2): Bool = {
        val ccd_trigger = Module(new CcdTrigger(nStages))
        ccd_trigger.io.fromClock := fromClock
        ccd_trigger.io.toClock := toClock
        ccd_trigger.io.fromReset := fromReset
        ccd_trigger.io.toReset := toReset
        ccd_trigger.io.triggerIn := triggerIn
        ccd_trigger.io.triggerOut
    }
}

/*
/**
 * Utility class for transferring counting status between clock domains.
 * @param fromClock Clock of the clock domain in which status transferred
 * @param toClock   Clock of the clock domain in which status received
 * @param fromReset Reset in `fromClock` domain
 * @param toReset   Reset in `toClock` domain
 * @param width     Width in bits of the counter
 */
class CcdCounter(fromClock: Clock, toClock: Clock,
                 fromReset: Bool, toReset: Bool)
                (width: Int){
    require(width >= 1, "In CcdCounter, width must be >= 1.")

    private val cnt_a = withClockAndReset(fromClock, fromReset) {
        RegInit(0.U(width.W))
    }
    private val gray = withClockAndReset(fromClock, fromReset) {
        RegInit(0.U(width.W))
    }
    private val gray_sync = withClockAndReset(toClock, toReset) {
        val gray_ams = AntiMetastable(UInt(width.W))()
        gray_ams.input(gray)
    }
    private val bin_next = cnt_a + 1.U(width.W)
    private val gray_next = if(width > 1) bin_next ^ (bin_next >> 1).asUInt
                            else bin_next

    /**
     * Increase the counter by one step, use in `fromClock` domain.
     */
    def inc(en: Bool = true.B): Unit = {
        withClock(fromClock) {
            when(en) {
                cnt_a := bin_next
                gray := gray_next
            }
        }
    }

    /**
     * Assign to wire for getting the counter value, use in `fromClock` domain.
     * @return Counter value in `fromClock` domain.
     */
    def cnt_from(): UInt = {
        cnt_a
    }

    /**
     * Assign to wire for getting the counter value, use in `toClock` domain.
     * @return Counter value in `toClock` domain.
     */
    def cnt_to(): UInt = {
        (0 until width).foldLeft(0.U) { (acc, i) => {
            acc | ((gray_sync >> i).xorR << i)
        }}
    }
}

object CcdCounter {
    /**
     * Create an instance of Cross Clock Domain Counter.
     * @param fromClock Clock of the clock domain in which status transferred
     * @param toClock   Clock of the clock domain in which status received
     * @param fromReset Reset in `fromClock` domain
     * @param toReset   Reset in `toClock` domain
     * @param width     Width in bits of the counter
     * @return          The instance created
     */
    def apply(fromClock: Clock, toClock: Clock,
              fromReset: Bool, toReset: Bool)
             (width: Int) = {
        new CcdCounter(fromClock, toClock, fromReset, toReset)(width)
    }
}
*/

/**
 * Represent a hardware Cross Clock Domain Counter module.
 * @param width Width in bits of the counter
 */
class CcdCounterModule(width: Int) extends RawModule {
    require(width >= 1, "In CcdCounterModule, width must be >= 1.")
    override def desiredName: String = super.desiredName + s"_${width}b"

    val io = IO(new Bundle {
        val fromClock = Input(Clock())
        val toClock   = Input(Clock())
        val fromReset = Input(Bool())
        val toReset   = Input(Bool())
        val inc_from  = Input(Bool())
        val cnt_from  = Output(UInt(width.W))
        val cnt_to    = Output(UInt(width.W))
    })

    private val cnt_from = withClockAndReset(io.fromClock, io.fromReset) {
        RegInit(0.U(width.W))
    }
    private val gray = withClockAndReset(io.fromClock, io.fromReset) {
        RegInit(0.U(width.W))
    }
    private val gray_sync = withClockAndReset(io.toClock, io.toReset) {
        AntiMetastable._2ff(gray)()
    }
    private val bin_next = cnt_from + 1.U(width.W)
    private val gray_next =
        if(width > 1) bin_next ^ (bin_next >> 1).asUInt
        else bin_next

    withClock(io.fromClock) {
        when(io.inc_from) {
            cnt_from := bin_next
            gray     := gray_next
        }
    }

    io.cnt_from := cnt_from
    io.cnt_to :=
        (0 until width).foldLeft(0.U) {
            (acc, i) => {
                acc | ((gray_sync >> i).xorR << i)
            }
        }
}

/**
 * A wrapper class of CcdCounterModule for ease of use
 * @param fromClock Clock of clock domain in which the counter increase
 * @param toClock   Clock of clock domain in which the counter value received
 * @param fromReset Reset of `fromClock` domain
 * @param toReset   Reset of `toClock` domain
 * @param width     Width in bits of the counter
 * @param incCond   The condition of counter increasing
 */
class CcdCounter(fromClock: Clock, toClock: Clock,
                 fromReset: Bool, toReset: Bool)(
                 width: Int, incCond: Bool) {
    require(width > 0, "In CcdCounter, width must be >= 1.")

    val ccd_cnt = Module(new CcdCounterModule(width))
    ccd_cnt.io.fromClock := fromClock
    ccd_cnt.io.toClock := toClock
    ccd_cnt.io.fromReset := fromReset
    ccd_cnt.io.toReset := toReset
    ccd_cnt.io.inc_from := incCond

    /**
     * Get the counter value in `fromClock` domain
     *
     * @return The counter value
     */
    def cnt_from(): UInt = {
        ccd_cnt.io.cnt_from
    }

    /**
     * Get the counter value in `toClock` domain
     *
     * @return The counter value
     */
    def cnt_to(): UInt = {
        ccd_cnt.io.cnt_to
    }
}

object CcdCounter {
    /**
     * Create an instance of CcdCounter, with a CcdCounterModule in it and make
     * some connections.
     * @param fromClock Clock of clock domain in which the counter increase
     * @param toClock   Clock of clock domain in which the counter value received
     * @param fromReset Reset of `fromClock` domain
     * @param toReset   Reset of `toClock` domain
     * @param width     Width in bits of the counter
     * @param incCond   The condition of counter increasing
     * @return          The instance created
     */
    def apply(fromClock: Clock, toClock: Clock, fromReset: Bool, toReset: Bool)(width: Int, incCond: Bool) = {
        new CcdCounter(fromClock, toClock, fromReset, toReset)(width, incCond)
    }
}

/**
 * Represent a hardware Dual Clock Fifo with native fifo interfaces.
 * @param gen           Generator of data
 * @param addrWidth     Width in bits of the inner address,
 *                      fifo depth = 2**addrWidth - 1
 * @param wrHasDataCnt  Set to true if data count is needed in write side
 * @param rdHasDataCnt  Set to true if data count is needed in read side
 * @param wrHasWrRdCnt  Set to true if write count and read count are needed
 *                      in write side
 * @param rdHasWrRdCnt  Set to true if write count and read count are needed
 *                      in read side
 * @tparam T            Type of data
 */
class DcFifo[T <: Data](gen: T, addrWidth: Int,
                        wrHasDataCnt: Boolean = false,
                        rdHasDataCnt: Boolean = false,
                        wrHasWrRdCnt: Boolean = false,
                        rdHasWrRdCnt: Boolean = false) extends RawModule {
    require(addrWidth >= 1 && addrWidth < 64, "In DcFifo, addrWidth must be in [1, 63]")
    require(!(!wrHasDataCnt && wrHasWrRdCnt), "In DcFifo, when wrHasDataCnt == false, wrHasWrRdCnt is not allowed to be true!")
    require(!(!rdHasDataCnt && rdHasWrRdCnt), "In DcFifo, when rdHasDataCnt == false, rdHasWrRdCnt is not allowed to be true!")

    val io = IO(new Bundle {
        val wrClock = Input(Clock())
        val rdClock = Input(Clock())
        val wrReset = Input(Bool())
        val rdReset = Input(Bool())
        val w = FifoIO.Writee(gen)
        val wcnt = if(wrHasDataCnt) Some(FifoIO.CntProvider(addrWidth, wrHasWrRdCnt))
                   else None
        val r = FifoIO.Readee(gen)
        val rcnt = if(rdHasDataCnt) Some(FifoIO.CntProvider(addrWidth, rdHasWrRdCnt))
                   else None
    })
    val m: BigInt = 1 << addrWidth
    val capacity: BigInt = m - 1

    val ccd_wr_cnt = CcdCounter(io.wrClock, io.rdClock,
                                io.wrReset, io.rdReset)(addrWidth, io.w.write)
    val ccd_rd_cnt = CcdCounter(io.rdClock, io.wrClock,
                                io.rdReset, io.wrReset)(addrWidth, io.r.read)

    val sdc_ram = SyncReadMem(m, gen)
    when(io.w.write) { sdc_ram.write(ccd_wr_cnt.cnt_from(), io.w.data, io.wrClock) }
    val qout_b = sdc_ram.read(ccd_rd_cnt.cnt_from(), io.rdClock)

    withClockAndReset(io.rdClock, io.rdReset) {
        val rd_dly = RegNext(io.r.read, false.B)
        val qout_b_reg = RegEnable(qout_b, 0.U.asTypeOf(gen), rd_dly)
        io.r.data := Mux(rd_dly, qout_b, qout_b_reg)
    }

    val w_data_cnt = ccd_wr_cnt.cnt_from() - ccd_rd_cnt.cnt_to()
    io.w.full     := w_data_cnt === capacity.U
    val r_data_cnt = ccd_wr_cnt.cnt_to() - ccd_rd_cnt.cnt_from()
    io.r.empty    := r_data_cnt === 0.U

    io.wcnt.foreach(wc => {
        wc.dataCnt := w_data_cnt
        wc.writeCnt.foreach(_ := ccd_wr_cnt.cnt_from())
        wc.readCnt.foreach(_ := ccd_rd_cnt.cnt_to())
    })
    io.rcnt.foreach(rc => {
        rc.dataCnt := r_data_cnt
        rc.writeCnt.foreach(_ := ccd_wr_cnt.cnt_to())
        rc.readCnt.foreach(_ := ccd_rd_cnt.cnt_from())
    })
}

/*
object DcFifo {
    /**
     * Create an instance of DcFifo.
     *
     * @param gen          Generator of data
     * @param addrWidth    Width in bits of the inner address,
     *                     fifo depth = 2**addrWidth - 1
     * @param wrHasDataCnt Set to true if data count is needed in write side
     * @param rdHasDataCnt Set to true if data count is needed in read side
     * @param wrHasWrRdCnt Set to true if write count and read count are needed
     *                     in write side
     * @param rdHasWrRdCnt Set to true if write count and read count are needed
     *                     in read side
     * @tparam T Type of data
     * @return The instance created
     * @example {{{
     *              val dcfifo = DcFifo(UInt(32.W), 10, true, true)
     *              dcfifo.wrClock := clk_wr
     *              dcfifo.rdClock := clk_rd
     *              dcfifo.wrReset := rst_wr
     *              dcfifo.rdReset := rst_rd
     *              io.fifo_wr <> dcfifo.w
     *              io.fifo_rd <> dcfifo.r
     *              io.fifo_wrcnt <> dcfifo.wcnt.get
     *              io.fifo_rdcnt <> dcfifo.rcnt.get
     * }}}
     */
    def apply[T <: Data](gen: T, addrWidth: Int,
                         wrHasDataCnt: Boolean = false, rdHasDataCnt: Boolean = false,
                         wrHasWrRdCnt: Boolean = false, rdHasWrRdCnt: Boolean = false): DcFifo[T] = {
        Module(new DcFifo(gen, addrWidth,
            wrHasDataCnt, rdHasDataCnt,
            wrHasWrRdCnt, rdHasWrRdCnt))
    }
}
 */

package examples {
    class anti_metastable_example extends Module {
        val io = IO(new Bundle {
            val in0 = Input(UInt(8.W))
            val out0 = Output(UInt(8.W))
            val in1 = Input(UInt(4.W))
            val out1 = Output(UInt(4.W))
        })
        val ams = AntiMetastable(UInt(8.W), 3)()
        io.out0 := ams.input(io.in0)
        io.out1 := AntiMetastable._2ff(io.in1)(
            Some(() => {
                (~0.U).asTypeOf(io.in1)
            })
        )
    }

    class edge2en_example extends Module {
        val io = IO(new Bundle {
            val in = Input(Bool())
            val out = Output(Bool())
            val rising = Output(Bool())
            val falling = Output(Bool())
        })
        val (r, f, o) = Edge2En(io.in, true)
        io.rising := r
        io.falling := f
        io.out := o
    }

    class ccd_trigger_example extends RawModule {
        val io = IO(new Bundle {
            val clk_a = Input(Clock())
            val clk_b = Input(Clock())
            val rst_a = Input(Bool())
            val rst_b = Input(Bool())
            val trig_in = Input(Bool())
            val trig_out = Output(Bool())
        })
        io.trig_out := CcdTrigger(io.clk_a, io.clk_b, io.rst_a, io.rst_b)(io.trig_in)
    }

    class ccd_counter_example extends RawModule {
        val io = IO(new Bundle {
            val clk_a = Input(Clock())
            val clk_b = Input(Clock())
            val rst_a = Input(Bool())
            val rst_b = Input(Bool())
            val inc_a = Input(Bool())
            val cnt_a = Output(UInt(8.W))
            val cnt_b = Output(UInt(8.W))
        })
        val ccdcnt = CcdCounter(io.clk_a, io.clk_b, io.rst_a, io.rst_b)(8, io.inc_a)
        io.cnt_a := ccdcnt.cnt_from()
        io.cnt_b := ccdcnt.cnt_to()
    }

    class dcfifo_example extends RawModule {
        val dType = UInt(32.W)
        val aw = 10
        val w_has_dc = true
        val r_has_dc = true
        val w_has_wrc = false
        val r_has_wrc = false
        val io = FlatIO(new Bundle {
            val clk_wr = Input(Clock())
            val clk_rd = Input(Clock())
            val reset_wr = Input(Bool())
            val reset_rd = Input(Bool())
            val w = FifoIO.Writee(dType)
            val wc = if (w_has_dc) Some(FifoIO.CntProvider(aw, w_has_wrc)) else None
            val r = FifoIO.Readee(dType)
            val rc = if (r_has_dc) Some(FifoIO.CntProvider(aw, r_has_wrc)) else None
        })
        val dcfifo = Module(new DcFifo(dType, aw, w_has_dc, r_has_dc, w_has_wrc, r_has_wrc))
        io.clk_wr <> dcfifo.io.wrClock
        io.clk_rd <> dcfifo.io.rdClock
        io.reset_wr <> dcfifo.io.wrReset
        io.reset_rd <> dcfifo.io.rdReset
        io.w <> dcfifo.io.w
        io.r <> dcfifo.io.r
        if (w_has_dc)
            io.wc.get <> dcfifo.io.wcnt.get
        if (r_has_dc)
            io.rc.get <> dcfifo.io.rcnt.get
    }
}

override def desiredName: String = super.desiredName + s"_${(1<<addrWidth)}_x_${gen.typeName}"


/*
 * MIT License
 *
 * Copyright (c) 2025 loykylewong (loywong@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package loywong

import chisel3._

object Decoder {

}

package examples {
    class decoder_example(INW: Int = 4) extends Module {
        val io = IO(new Bundle {
            val in = Input(UInt(INW.W))
            val out = Output(UInt((1 << INW).W))
        })
        io.out := Decoder(io.in)
    }
}

def apply(in:UInt) : UInt = { 1.U << in }


/*
 * MIT License
 *
 * Copyright (c) 2025 loykylewong (loywong@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package loywong

import chisel3._
import chisel3.util._

package examples {
    class delay_chain_example extends Module {
        val n = 16
        val dw = 8

            val en = Input(Bool())
            val in = Input(UInt(dw.W))
            val out = Output(UInt(dw.W))
        })
        // user chisel3.util.ShiftRegister directly
        if(n < 16)
            io.out := ShiftRegister(io.in, n, io.en)
        else
            io.out := ShiftRegister.mem(io.in, n, io.en, true, None)
    }
}