/*
 * 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 loywongtest

import chisel3._
import chisel3.util._
import chiseltest._
import chiseltest.internal.TesterThreadList

import scala.util.Random
import scala.collection.Searching._
import scala.collection.mutable.ArrayBuffer
// import scala.reflect.{ClassTag, classTag}
import org.apache.commons.math3.distribution.PoissonDistribution
import loywong._
import loywong.util._

/**
 * A class collects stream signals
 * @param str   the stream io (IrrevocableIO) to be monitored
 * @tparam T    type of `str.bits`, must be inherited from StrBits
 */
abstract class StrMonitorSignals[T <: StrBits](str: IrrevocableIO[T]) {
    private val gapsArray: ArrayBuffer[Int] = ArrayBuffer[Int]()
    private val clksArray: ArrayBuffer[Long] = ArrayBuffer[Long]()
    private var lastClkCnt: Long = 0
    /**
     * Append gap to gapsArray, calculate and append element to clksArray.
     * @param clkCnt    clockStepCount after current beat
     */
    protected def appendGaps(clkCnt: Long): Unit = {
        gapsArray += {
            if(clksArray.isEmpty) clkCnt - lastClkCnt
            else clkCnt - clksArray.last
        }.toInt
        clksArray += clkCnt
        lastClkCnt = clkCnt
    }

    private val dataArray: ArrayBuffer[BigInt] = ArrayBuffer[BigInt]()
    private val strbArray: ArrayBuffer[BigInt] = ArrayBuffer[BigInt]()
    private val keepArray: ArrayBuffer[BigInt] = ArrayBuffer[BigInt]()
    private val lastArray: ArrayBuffer[Boolean] = ArrayBuffer[Boolean]()
    private val idArray: ArrayBuffer[Long] = ArrayBuffer[Long]()
    private val destArray: ArrayBuffer[Long] = ArrayBuffer[Long]()
    private val userArray: ArrayBuffer[BigInt] = ArrayBuffer[BigInt]()

    // non-removable bytes
    private val byteArray: ArrayBuffer[Byte] = ArrayBuffer[Byte]()
    // strb associated with each non-removable bytes
    private val bStrbArray: ArrayBuffer[Boolean] = ArrayBuffer[Boolean]()
    // id associated to each non-removable bytes
    private val bIdArray: ArrayBuffer[Long] = ArrayBuffer[Long]()
    // dest associated to each non-removable bytes
    private val bDestArray: ArrayBuffer[Long] = ArrayBuffer[Long]()
    // user associated with each non-removable bytes, if applicable
    private val bUserArray: ArrayBuffer[BigInt] = ArrayBuffer[BigInt]()

    private val nUserUnitBits = str.bits.userBitsPerDataByte
    private val userUnitMask = (BigInt(1) << nUserUnitBits) - 1

    protected def appendSignalsByPeek(): (
            Option[BigInt],     // data
            Option[BigInt],     // strb
            Option[BigInt],     // keep
            Option[Boolean],    // last
            Option[Long],       // id
            Option[Long],       // dest
            Option[BigInt]      // user
            ) = {
        val data = str.bits.peekDataIfExist
        val strb = str.bits.peekStrbIfExist
        val keep = str.bits.peekKeepIfExist
        val last = str.bits.peekLastIfExist
        val id = str.bits.peekIdIfExist
        val dest = str.bits.peekDestIfExist
        val user = str.bits.peekUserIfExist
        appendSignalsManually(data, strb, keep, last, id, dest, user)
        (data, strb, keep, last, id, dest, user)
    }

    protected def appendSignalsManually(data: Option[BigInt],
                                        strb: Option[BigInt],
                                        keep: Option[BigInt],
                                        last: Option[Boolean],
                                        id  : Option[Long],
                                        dest: Option[Long],
                                        user: Option[BigInt]): Unit = {
        data.foreach(d => dataArray += d)
        strb.foreach(s => strbArray += s)
        keep.foreach(k => keepArray += k)
        last.foreach(l => lastArray += l)
        id.foreach(i => idArray += i.toLong)
        dest.foreach(d => destArray += d.toLong)
        user.foreach(u => userArray += u)
        val s = strb.getOrElse(str.bits.strbMask)
        val k = keep.getOrElse(str.bits.keepMask)
        val d = data.getOrElse(BigInt(0))
        val i = id.getOrElse(0L)
        val t = dest.getOrElse(0L)
        val u = user.getOrElse(BigInt(0))
        for (b <- 0 until k.bitLength) {
            if (k.testBit(b)) {
                if (str.bits.hasData) {
                    byteArray += ((d >> (8 * b)) & 0xFF).toByte
                    if (str.bits.hasStrb) {
                        bStrbArray += s.testBit(b)
                    }
                }
                if (str.bits.hasId) {
                    bIdArray += i
                }
                if (str.bits.hasDest) {
                    bDestArray += t
                }
                if (str.bits.hasUser) {
                    if (str.bits.asInstanceOf[StrHasUser].userAssociation == StrUserAssociation.SpreadInBytes)
                        bUserArray += (u >> (nUserUnitBits * b)) & userUnitMask
                    else
                        bUserArray += u
                }
            }
        }
    }

    def gapsSeq: Seq[Int] = gapsArray.toSeq
    def clksSeq: Seq[Long] = clksArray.toSeq
    def dataSeq: Seq[BigInt] = dataArray.toSeq
    def strbSeq: Seq[BigInt] = strbArray.toSeq
    def keepSeq: Seq[BigInt] = keepArray.toSeq
    def lastSeq: Seq[Boolean] = lastArray.toSeq
    def idSeq: Seq[Long] = idArray.toSeq
    def destSeq: Seq[Long] = destArray.toSeq
    def userSeq: Seq[BigInt] = userArray.toSeq

    def byteSeq: Seq[Byte] = byteArray.toSeq
    def bStrbSeq: Seq[Boolean] = bStrbArray.toSeq
    def bIdSeq: Seq[Long] = bIdArray.toSeq
    def bDestSeq: Seq[Long] = bDestArray.toSeq
    def bUserSeq: Seq[BigInt] = bUserArray.toSeq

    def clearAllGapsAndSignals(): Unit = {
        gapsArray.clear()
        clksArray.clear()
        dataArray.clear()
        strbArray.clear()
        keepArray.clear()
        lastArray.clear()
        idArray.clear()
        destArray.clear()
        userArray.clear()
        byteArray.clear()
        bStrbArray.clear()
        bIdArray.clear()
        bDestArray.clear()
        bUserArray.clear()
    }

    private def checkSeqWithRef[T](ref: Seq[T], arr: ArrayBuffer[T]): Int = {
        var errCnt = math.abs(ref.length - arr.length)
        (0 until math.min(ref.length, arr.length)).foreach(i => {
            if(ref(i) != arr(i))
                errCnt = errCnt + 1
        })
        errCnt
    }

    def checkDataSeq(refData: Seq[BigInt]): Int = checkSeqWithRef(refData, dataArray)
    def checkStrbSeq(refStrb: Seq[BigInt]): Int = checkSeqWithRef(refStrb, strbArray)
    def checkKeepSeq(refKeep: Seq[BigInt]): Int = checkSeqWithRef(refKeep, keepArray)
    def checkIdSeq(refId: Seq[Long]): Int = checkSeqWithRef(refId, idArray)
    def checkDestSeq(refDest: Seq[Long]): Int = checkSeqWithRef(refDest, destArray)
    def checkUserSeq(refUser: Seq[BigInt]): Int = checkSeqWithRef(refUser, userArray)

    def checkByteSeq(refBytes: Seq[Byte]): Int = checkSeqWithRef(refBytes, byteArray)
    def checkBStrbSeq(refBStrbs: Seq[Boolean]): Int = checkSeqWithRef(refBStrbs, bStrbArray)
    def checkBIdSeq(refBIds: Seq[Long]): Int = checkSeqWithRef(refBIds, bIdArray)
    def checkBDestSeq(refBDests: Seq[Long]): Int = checkSeqWithRef(refBDests, bDestArray)
    def checkBUserSeq(refBUsers: Seq[BigInt]): Int = checkSeqWithRef(refBUsers, bUserArray)

    private val dataHexWidth = str.bits.dataBytesCeil * 2
    private val dataFormat = s"d:%0${dataHexWidth}X"
    // private val strbHexWidth = math.ceil(str.bits.strStrbWidth / 4.0).toInt
    // private val strbFormat = s" s:%0${strbHexWidth}X"
    // private val keepHexWidth = math.ceil(str.bits.strKeepWidth / 4.0).toInt
    // private val keepFormat = s" k:%0${keepHexWidth}X"
    private val idHexWidth = math.ceil(str.bits.idWidth / 4.0).toInt
    private val idFormat = s" \u001b[38;2;128;128;128mi:\u001b[0m%0${idHexWidth}X"
    private val destHexWidth = math.ceil(str.bits.destWidth / 4.0).toInt
    private val destFormat = s" \u001b[38;2;128;128;128md:\u001b[0m%0${destHexWidth}X"
    private val userHexWidth = math.ceil(str.bits.userWidth / 4.0).toInt
    private val userFormat = s" \u001b[38;2;128;128;128mu:\u001b[0m%0${userHexWidth}X"
    private val emptyStringLen = /*2 +*/ dataHexWidth +
            // { if(str.bits.hasStrb) 3 + strbHexWidth else 0 } +
            // { if(str.bits.hasKeep) 3 + keepHexWidth else 0 } +
            // { if(str.bits.hasLast) 2 else 0 } +
            { if(str.bits.hasId) 3 + idHexWidth else 0 } +
            { if(str.bits.hasDest) 3 + destHexWidth else 0 } +
            { if(str.bits.hasUser) 3 + userHexWidth else 0 }
    private val emptyFormat = s"%${emptyStringLen}s"

    private def appendDataString(sigStr: StringBuilder, data: BigInt, strb: BigInt, keep: BigInt, last: Boolean): Unit = {
        if(last) {
            sigStr.append("\u001b[7m")
        }
        for(i <- str.bits.dataBytesCeil - 1 to 0 by -1) {
            if(!keep.testBit(i)) {
                // gray
                sigStr.append("\u001b[38;2;128;128;128m%02X\u001b[0m".format((data >> (i * 8)) & 0xFF))
            }
            else if(!strb.testBit(i)) {
                // normal
                sigStr.append("%02X\u001b[0m".format((data >> (i * 8)) & 0xFF))
            }
            else {
                // bold
                sigStr.append("\u001b[1m%02X\u001b[0m".format((data >> (i * 8)) & 0xFF))
            }
        }
    }

    /**
     * convert signals in the `nClock`-th clock cycle to human-readable string,
     * in which:
     *
     *  - data is printed in hex, byte:
     *
     *  is gray if associated keep is low;
     *
     *  is normal if associated keep is high and strb is low;
     *
     *  is bold if associated keep and strb are both high.
     *
     *  - id, dest and user are printed in hex with prefix `i:`, `d:` and `u:`.
     *
     * @param nClock    signals in which cycle to be converted
     * @return          human-readable string
     */
    def signalStringOfClock(nClock: Long): String = {
        val sigStr = new StringBuilder()
        clksArray.search(nClock) match {
            case Found(i) => {
                val strb = if(str.bits.hasStrb) strbArray(i) else str.bits.strbMask
                val keep = if(str.bits.hasKeep) keepArray(i) else str.bits.keepMask
                val last = if(str.bits.hasLast) lastArray(i) else false
                appendDataString(sigStr, dataArray(i), strb, keep, last)
                sigStr.append(
                    if (str.bits.hasId) {
                        idFormat.format(idArray(i))
                    } else ""
                )
                sigStr.append(
                    if (str.bits.hasDest) {
                        destFormat.format(destArray(i))
                    } else ""
                )
                sigStr.append(
                    if (str.bits.hasUser) {
                        userFormat.format(userArray(i))
                    } else ""
                )
                sigStr.toString()
            }
            case _ => {
                emptyFormat.format("")
            }
        }
    }
}

/**
 * A stream source for test-bench.
 * @param clk   Clock associated with stream IO (IrrevocableIO)
 * @param ds    Down-stream IO to be tested
 * @tparam T    Type of bits in IrrevocableIO
 */
class StrTestSource[T <: StrHasData](clk: Clock, ds: IrrevocableIO[T])
extends StrMonitorSignals[T](ds) {

    ds.valid.poke(false)

    private val bytesPerBeat = ds.bits.dataBytesCeil
    private val strbMask = (BigInt(1) << bytesPerBeat) - 1
    private val keepMask = (BigInt(1) << bytesPerBeat) - 1
    private val userUnitWidth = ds.bits.userBitsPerDataByte
    private val userUnitMask = (BigInt(1) << userUnitWidth) - 1

    private var poisson = new PoissonDistribution(1.0)
    private var fixedInterval = 0
    private var isRandomInterval = false

    def setThrottle(isRandomThrottle: Boolean, throttleRatio: Double): Unit = {
        poisson = new PoissonDistribution(if (throttleRatio <= 0.0) Double.MinPositiveValue else throttleRatio)
        fixedInterval = math.round(throttleRatio).toInt
        isRandomInterval = isRandomThrottle
    }

    private var genRandomKeep = false
    private var randomKeepDensity = 0.5

    /**
     * Enable random keep generation in `sendBeatsByBytes`
     * @param desireDensity desire keep bit density (duty of non-removable
     *                      bytes)
     *                      - if `strbSeq`, `keepSeq`, `idSeq` and `destSeq`
     *                      are empty in `sendBeatsByBytes`, `desireDensity`
     *                      take effects;
     *                      - otherwise, actual density will be overridden by
     *                      `byteSeq.length / (totalBeats * nBytesOfData)`, in
     *                      which totalBeats = length of `strbSeq`, `keepSeq`,
     *                      `idSeq` or `destSeq`
     * @note if random keep generation enabled and `keepSeq` is not empty in
     *       `sendBeatsByBytes`, elements in `keepSeq` will be overridden by
     *       generated random keep.
     * @note OBSOLETE, use:
     *       1. randomBytes(), randomBStrbs(), randomBUsers() and.
     *       1. convertBytesToBeats() and,
     *       1. sendBeats() instead.
     */
    def enableRandomKeep(desireDensity: Double = 0.5): Unit = {
        require(1.0/256.0 <= desireDensity && desireDensity <= 255.0/256.0)
        require(ds.bits.hasKeep)
        genRandomKeep = true
        randomKeepDensity = desireDensity
    }

    /**
     * Disable random keep generation in `sendBeatsByBytes`
     * @note if random keep generation disabled and `keepSeq` is empty in
     *       `sendBeatsByBytes`, `keep` in all beats will be all ones.
     * @note OBSOLETE, use:
     *       1. randomBytes(), randomBStrbs(), randomBUsers() and.
     *       1. convertBytesToBeats() and,
     *       1. sendBeats() instead.
     */
    def disableRandomKeep(): Unit = {
        genRandomKeep = false
    }

    /**
     * Wait a number of clock cycles specified by arg `isRandomThrottle` and
     * arg `throttleRatio` in method `setThrottle`.
     * @return  Actual cycles of clock waited
     */
    private def waitGap(): Int = {
        val interval =
            if (isRandomInterval) poisson.sample()
            else fixedInterval
        if (interval > 0) {
            clk.step(interval)
        }
        interval
    }

    /**
     * Send a transaction beat.
     *
     * @param data  Data to be sent
     * @param last  Whether send a high on last signal if the bits in
     *              IrrevocableIO contains last signal (inherit StrWithLast)
     * @param keep  Keep signal to be sent, default all ones, if the bits in
     *              IrrevocableIO contains keep signal (inherit StrWithKeep)
     * @return      Clock cycles spent on waiting ready signal
     */
    private def sendBeat(data: BigInt,
                         strb: BigInt = strbMask,
                         keep: BigInt = keepMask,
                         last: Boolean = false,
                         id  : Long = 0L,
                         dest: Long = 0L,
                         user: BigInt = BigInt(0)
                        ): Int = {

        val d = ds.bits.pokeDataIfExist(data)
        val l = ds.bits.pokeLastIfExist(last)
        val s = ds.bits.pokeStrbIfExist(strb)
        val k = ds.bits.pokeKeepIfExist(keep)
        val i = ds.bits.pokeIdIfExist(id)
        val t = ds.bits.pokeDestIfExist(dest)
        val u = ds.bits.pokeUserIfExist(user)

        appendSignalsManually(d, s, k, l, i, t, u)

        ds.valid.poke(true)
        var gap = 0
        while (!ds.ready.peekBoolean()) {
            clk.step(1)
            gap = gap + 1
        }
        clk.step(1)
        ds.valid.poke(false)

        appendGaps(clk.getStepCount)

        gap
    }

    private def prepareDataByKeep(bytes: Seq[Byte], currIdx: Int, keep: BigInt): (BigInt, Int) = {
        var idx = currIdx
        var data: BigInt = 0
        for (b <- 0 until bytesPerBeat) {
            if(keep.testBit(b)) {
                data = data | (BigInt(bytes(idx) & 0xFF) << (b * 8))
                idx = idx + 1
            }
        }
        (data, idx)
    }

    private def prepareUserByKeep(users: Seq[BigInt], currIdx: Int, keep: BigInt): (BigInt, Int) = {
        var idx = currIdx
        var user: BigInt = 0
        for (b <- 0 until bytesPerBeat) {
            if(keep.testBit(b)) {
                user = user | (users(idx) << (b * userUnitWidth))
                idx = idx + 1
            }
        }
        (user, idx)
    }

    private def randBigInt(nBits: Int): BigInt = {
        val r = (0 until nBits by 32).foldLeft(BigInt(0))(
            (acc, i) => acc | (BigInt(Random.nextInt() & 0xFFFFFFFFL) << i)
        )
        r & ((BigInt(1) << nBits) - 1)
    }
    def randomBytes(nBytes: Int): Seq[Byte] = {
        Random.nextBytes(nBytes).toSeq
    }
    def randomBStrbs(nBytes: Int, density: Double = 1.0): Seq[Boolean] = {
        val thr = math.round(nBytes * density).toInt
        Random.shuffle((0 until nBytes).toList).map(i => i < thr)
    }
    def randomBUsers(nBytes: Int, unitWidth: Int = userUnitWidth): Seq[BigInt] = {
        require(unitWidth > 0)
        Seq.fill(nBytes)(randBigInt(unitWidth))
    }
    def randomData(nBeats: Int): Seq[BigInt] = {
        Seq.fill(nBeats)(randBigInt(ds.bits.dataWidth))
    }
    def randomStrbs(nBeats: Int, bytesPerBeat: Int = bytesPerBeat, density: Double = 1.0): Seq[BigInt] = {
        require(bytesPerBeat > 0)
        val nBytes = nBeats * bytesPerBeat
        val strbMask = (BigInt(1) << bytesPerBeat) - 1
        val strb = Array.fill(nBeats)(strbMask)
        randomBStrbs(nBytes, density).zipWithIndex.foreach { case(b, i) =>
            val beatIdx = i / bytesPerBeat
            val bitPos = i % bytesPerBeat
            if(!b)
                strb(beatIdx) = strb(beatIdx) & ~(BigInt(1) << bitPos)
        }
        strb.toSeq
    }
    def randomIds(nBeats: Int, width: Int = ds.bits.idWidth, immutablePeriod: Int = 1): Seq[Long] = {
        require(width > 0)
        require(immutablePeriod > 0)
        require(nBeats % immutablePeriod == 0)
        var lastRand = 0L
        Seq.tabulate(nBeats)( i => {
            if (i % immutablePeriod == 0) {
                lastRand = Random.nextLong() & ((1L << width) - 1)
                lastRand
            }
            else lastRand
        })
    }
    def randomDests(nBeats: Int, width: Int = ds.bits.destWidth, immutablePeriod: Int = 1): Seq[Long] = {
        require(width > 0)
        require(immutablePeriod > 0)
        require(nBeats % immutablePeriod == 0)
        var lastRand = 0L
        Seq.tabulate(nBeats)( i => {
            if (i % immutablePeriod == 0) {
                lastRand = Random.nextLong() & ((1L << width) - 1)
                lastRand
            }
            else lastRand
        })
    }
    def randomUsers(nBeats: Int, width: Int = ds.bits.userWidth, immutablePeriod: Int = 1): Seq[BigInt] = {
        require(width > 0)
        require(immutablePeriod > 0)
        require(nBeats % immutablePeriod == 0)
        var lastRand = BigInt(0)
        Seq.tabulate(nBeats)(i => {
            if (i % immutablePeriod == 0) {
                lastRand = randBigInt(width)
                lastRand
            }
            else lastRand
        })
    }

    /**
     * Convert non-removable bytes, associated strb and user to beats.
     * @param nBeats    Number of desired beats, if `bytes.length < nDataBytes
     *                  * nBeats`, random keep will be generated, otherwise,
     *                  keep will be all ones.
     * @param bytes     Non-removable bytes
     * @param strb      Strb(s) associated with each byte in `bytes`
     * @param user      User(s) associated with each byte in `bytes`
     * @return
     */
    def convertBytesToBeats(nBeats: Int,
                            bytes: Seq[Byte],
                            strb: Seq[Boolean] = Seq.empty[Boolean],
                            user: Seq[BigInt] = Seq.empty[BigInt]
                           ): (Seq[BigInt], Seq[BigInt], Seq[BigInt], Seq[BigInt]) = {

        require(bytes.nonEmpty)
        require(strb.isEmpty || strb.length == bytes.length)
        require(user.isEmpty || user.length == bytes.length)

        val minBeats = (bytes.length - 1) / bytesPerBeat + 1
        val totalBeats = math.max(nBeats, minBeats)
        val totalRawBytes = totalBeats * bytesPerBeat
        val totalKeepBytes = bytes.length

        val dataArray = Array.fill(totalBeats)(BigInt(0))
        val strbArray = Array.fill(totalBeats)(strbMask)
        val keepArray = Array.fill(totalBeats)(keepMask)
        val userArray = Array.fill(totalBeats)(BigInt(0))

        val kbits = Random.shuffle((0 until totalRawBytes).toList).map(i => i < totalKeepBytes)
        kbits.zipWithIndex.foreach {
            case (k, i) => if(!k) {
                val beatIdx = i / bytesPerBeat
                val byteIdx = i % bytesPerBeat
                keepArray(beatIdx) = keepArray(beatIdx) & ~(BigInt(1) << byteIdx)
            }
        }
        // val nrbytes = keepArray.foldLeft(0)((acc, k) => acc + k.popCount)
        // require(nrbytes == totalKeepBytes)

        var byteIdx = 0
        keepArray.zipWithIndex.foreach { case(keep, beatIdx) =>
            for (i <- 0 until bytesPerBeat) {
                if (keep.testBit(i)) {
                    dataArray(beatIdx) = dataArray(beatIdx) | (BigInt(bytes(byteIdx) & 0xFF) << (i * 8))
                    if(strb.nonEmpty && !strb(byteIdx))
                        strbArray(beatIdx) &= ~(BigInt(1) << i)
                    if(user.nonEmpty)
                        userArray(beatIdx) = userArray(beatIdx) | ((user(byteIdx) & userUnitMask) << (i * userUnitWidth))
                    byteIdx += 1
                }
            }
        }
        // require(byteIdx == bytes.length)
        (dataArray.toSeq, strbArray.toSeq, keepArray.toSeq, userArray.toSeq)
    }

    /**
     * Send beats by given signals of each beat.
     *
     * @param genLastAtLastBeat Whether generating last at the last beat,
     *                          this requires io.bits has last.
     * @param dataSeq           Contains data to be sent in each beat.
     * @param strbSeq           Contains strb to be sent in each beat,
     *                          default empty and all ones will be sent.
     * @param keepSeq           Contains keep to be sent in each beat,
     *                          default empty and all ones will be sent.
     * @param idSeq             Contains id to be sent in each beat,
     *                          default empty and all zeros will be sent.
     * @param destSeq           Contains dest to be sent in each beat,
     *                          default empty and all zeros will be sent.
     * @param userSeq           Contains user to be sent in each beat
     *                          default empty and all zeros will be sent.
     * @note
     *          1. When ds.bits contains no strb, keep, id, dest or user,
     *          corresponding Seq(s) will be ignored.
     *          1. If a Seq is not empty, its length must be equal to dataSeq.
     *          1. Total number of beats is equal to length of dataSeq.
     *          1. If random data needed, use:
     *              I. randomData(), randomStrbs(), randomIds(), randomDests()
     *              and/or randomUsers(), if keep is not needed;
     *              I. randomBytes(), randomBStrbs() and/or randomBUsers() with
     *              convertBytesToBeats(), if keep is needed.
     */
    def sendBeats(genLastAtLastBeat: Boolean,
                  dataSeq: Seq[BigInt] = Seq.empty[BigInt],
                  strbSeq: Seq[BigInt] = Seq.empty[BigInt],
                  keepSeq: Seq[BigInt] = Seq.empty[BigInt],
                  idSeq: Seq[Long] = Seq.empty[Long],
                  destSeq: Seq[Long] = Seq.empty[Long],
                  userSeq: Seq[BigInt] = Seq.empty[BigInt]): TesterThreadList = {

        if (genLastAtLastBeat) require(ds.bits.hasLast)
        require(strbSeq.isEmpty || strbSeq.length == dataSeq.length)
        require(keepSeq.isEmpty || keepSeq.length == dataSeq.length)
        require(idSeq.isEmpty || idSeq.length == dataSeq.length)
        require(destSeq.isEmpty || destSeq.length == dataSeq.length)
        require(userSeq.isEmpty || userSeq.length == dataSeq.length)

        clearAllGapsAndSignals()

        val thread = fork {
            dataSeq.zipWithIndex.foreach {
                case (d, i) => {
                    val last = (i == dataSeq.length - 1) && genLastAtLastBeat
                    val strb = if (strbSeq.isEmpty) strbMask else strbSeq(i)
                    val keep = if (keepSeq.isEmpty) keepMask else keepSeq(i)
                    val id = if (idSeq.isEmpty) 0L else idSeq(i)
                    val dest = if (destSeq.isEmpty) 0L else destSeq(i)
                    val user = if (userSeq.isEmpty) BigInt(0) else userSeq(i)
                    waitGap()
                    sendBeat(d, strb, keep, last, id, dest, user)
                }
            }
        }
        thread
    }

    /**
     * Send beats by given non-removable bytes.
     *
     * @param genLastAtLastBeat Whether generating last at the last beat,
     *                          this requires io.bits has last
     * @param byteSeq           Contains valid data bytes to be sent
     * @param strbSeq           Contains strb to be sent in each beat,
     *                          default empty, all ones will be sent
     * @param keepSeq           Contains keep to be sent in each beat,
     *                          default empty, all ones will be sent
     * @param idSeq             Contains id to be sent in each beat,
     *                          default empty, all zeros will be sent
     * @param destSeq           Contains dest to be sent in each beat,
     *                          default empty, all zeros will be sent
     * @param userSeq           Contains user to be sent associated with data
     *                          bytes, length must be equal to length of
     *                          byteSeq, default empty, all zeros will be sent
     * @note
     *          - When ds.bits contains no strb, keep, id, dest or user, Seq(s)
     *          will be ignored.
     *          - {{{
     *              total_num_of_beats =
     *                  if (strbSeq.nonEmpty) strbSeq.length
     *                  else if (keepSeq.nonEmpty) keepSeq.length
     *                  else if (idSeq.nonEmpty)   idSeq.length
     *                  else if (destSeq.nonEmpty) destSeq.length
     *                  else if(!genRandomKeep)
     *                      byteSeq.length / bytesPerBeat
     *                  else
     *                      ceil( byteSeq.length
     *                            / randomKeepDensity
     *                            / bytesPerBeat)
     *          }}}
     *          - When keepSeq is not empty, total count of high bit in keepSeq
     *          must be equal to length of dataSeq
     *          - When random keep enabled, elements in keepSeq is ignored
     *
     * @note OBSOLETE, since it's too difficult to use, use:
     *       1. convertBytesToBeats() and,
     *       1. sendBeats() instead.
     *
     *       if random data needed, user randomBytes(), randomBStrbs() and/or
     *       randomBUsers() first.
     */
    def sendBeatsByBytes(genLastAtLastBeat: Boolean,
                  byteSeq: Seq[Byte] = Seq.empty[Byte],
                  strbSeq: Seq[BigInt] = Seq.empty[BigInt],
                  keepSeq: Seq[BigInt] = Seq.empty[BigInt],
                  idSeq: Seq[Long] = Seq.empty[Long],
                  destSeq: Seq[Long] = Seq.empty[Long],
                  userSeq: Seq[BigInt] = Seq.empty[BigInt]): TesterThreadList = {

        require(ds.bits.userWidth % bytesPerBeat == 0)

        if (genLastAtLastBeat) require(ds.bits.hasLast)
        val (totalBeats, totalRawBytes) = {
            if (strbSeq.nonEmpty)
                (strbSeq.length, strbSeq.length * bytesPerBeat)
            else if (keepSeq.nonEmpty)
                (keepSeq.length, keepSeq.length * bytesPerBeat)
            else if (idSeq.nonEmpty)
                (idSeq.length, idSeq.length * bytesPerBeat)
            else if (destSeq.nonEmpty)
                (destSeq.length, destSeq.length * bytesPerBeat)
            else if(!genRandomKeep) {
                val beats = (byteSeq.length - 1) / bytesPerBeat + 1
                val rawBytes = beats * bytesPerBeat
                (beats, rawBytes)
            }
            else {
                val beats = math.ceil(byteSeq.length / randomKeepDensity / bytesPerBeat).toInt
                val rawBytes = beats * bytesPerBeat
                (beats, rawBytes)
            }
        }
        val totalKeepBytes = {
            if (genRandomKeep)
                byteSeq.length
            else if (keepSeq.nonEmpty)
                keepSeq.foldLeft(0)((acc, k) => acc + k.popCount)
            else
                totalRawBytes
        }

        require(totalKeepBytes <= totalRawBytes)
        require(byteSeq.length == totalKeepBytes)
        require(strbSeq.isEmpty || strbSeq.length == totalBeats)
        require(keepSeq.isEmpty || keepSeq.length == totalBeats)
        require(idSeq.isEmpty || idSeq.length == totalBeats)
        require(destSeq.isEmpty || destSeq.length == totalBeats)
        require(userSeq.isEmpty || userSeq.length == totalKeepBytes)

        clearAllGapsAndSignals()

        val actKeepSeq =
            if(!genRandomKeep) keepSeq
            else {
                val ks: Array[BigInt] = Array.fill[BigInt](totalBeats)(BigInt(0))
                val ki = scala.util.Random.shuffle((0 until totalRawBytes).toList).take(totalKeepBytes)
                ki.foreach {
                    i => {
                        ks(i / bytesPerBeat) = ks(i / bytesPerBeat).setBit(i % bytesPerBeat)
                    }
                }
                ks.toSeq
            }

        val thread = fork {
            var byteIdx = 0
            var userIdx = 0
            (0 until totalBeats).foreach {
                i => {
                    val last = (i == totalBeats - 1) && genLastAtLastBeat
                    val strb = if (strbSeq.isEmpty) strbMask else strbSeq(i)
                    val keep = if (actKeepSeq.isEmpty) keepMask else actKeepSeq(i)
                    val id = if (idSeq.isEmpty) 0L else idSeq(i)
                    val dest = if (destSeq.isEmpty) 0L else destSeq(i)
                    val (data, bIdx) = prepareDataByKeep(byteSeq, byteIdx, keep)
                    val (user, uIdx) = {
                        if (userSeq.isEmpty) (BigInt(0), 0)
                        else prepareUserByKeep(userSeq, userIdx, keep)
                    }
                    waitGap()
                    sendBeat(data, strb, keep, last, id, dest, user)

                    byteIdx = bIdx
                    userIdx = uIdx
                }
            }
        }
        thread
    }
}

/**
 * A stream sink for test-bench.
 * @param clk   Clock associated with stream IO (IrrevocableIO)
 * @param us    Up-stream IO to be tested
 * @tparam T    Type of bits in IrrevocableIO
 */
class StrTestSink[T <: StrHasData](clk: Clock, us: IrrevocableIO[T])
extends StrMonitorSignals[T](us) {

    require(us.bits.data.getWidth % 8 == 0)

    us.ready.poke(false)

    private var poisson = new PoissonDistribution(1.0)
    private var fixedInterval = 0
    private var isRandomInterval = false

    def setThrottle(isRandomThrottle: Boolean, throttleRatio: Double): Unit = {
        poisson = new PoissonDistribution(if (throttleRatio <= 0.0) Double.MinPositiveValue else throttleRatio)
        fixedInterval = math.round(throttleRatio).toInt
        isRandomInterval = isRandomThrottle
    }

    // private var dealKeep = true

    /**
     * Wait a number of clock cycles specified by arg `isRandomThrottle` and
     * arg `throttleRatio` in method `setThrottle`
     *
     * @return Actual cycles of clock waited
     */
    private def waitGap(): Int = {
        val interval =
            if (isRandomInterval) poisson.sample()
            else fixedInterval
        if (interval > 0) {
            clk.step(interval)
        }
        interval
    }

    /**
     * Receive a transaction beat.
     *
     * @return (last, gap):
     *         - last: if it's the last beat
     *         - gap: Clock cycles spent on waiting valid signal
     */
    private def recvBeat(): (Boolean, Int) = {
        us.ready.poke(true)

        var gap = 0
        while (!us.valid.peekBoolean()) {
            clk.step(1)
            gap = gap + 1
        }

        val (_, _, _, last, _, _, _) = appendSignalsByPeek()

        clk.step(1)
        us.ready.poke(false)

        appendGaps(clk.getStepCount)

        (last.getOrElse(false), gap)
    }

    /**
     * Receive a package by specified beats
     *
     * @param rxBeats Number of beats to be received.
     * @return The TestThread forked, user should use its `join()` method to
     *         wait the received operation finish before using any result
     */
    def recvBeats(rxBeats: Int): TesterThreadList = {

        clearAllGapsAndSignals()

        val thread = fork {
            for (i <- 0 until rxBeats) {
                waitGap()
                recvBeat()
            }
        }
        thread
    }

    /**
     * Receive a package to its end specified by the last signal in stream IO
     *
     * @return The TestThread forked, user should use its `join()` method to
     *         wait the received operation finish before using any result
     * @note The type of bits in IrrevocableIO must be inherited from trait
     *       `StrWithLast`
     */
    def recvBeatsToLast(): TesterThreadList = {

        require(us.bits.isInstanceOf[StrHasLast], s"In StrTestSink.recvPkgToLast(), type of bits ${us.bits.typeName} does NOT has last signal, you may prefer to use StrTestSink.recvPkgByNumBeats().")

        clearAllGapsAndSignals()

        val thread = fork {
            var last = false
            while (!last) {
                waitGap()
                val (lastBeat, _) = recvBeat()
                last = lastBeat
            }
        }
        thread
    }
}

def totalClocks: Long = clksArray.last + 1


/*
 * 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._
import chisel3.util._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers._
import loywong._

class test_accumulator extends AnyFlatSpec with ChiselScalatestTester {
    println()
    println("====================")
    println("Test Accumulator...")
    println("--------------------")

    "DUT" should "pass" in {
        val modu = 100
        test(new Accumulator(modu)).withAnnotations(Seq(WriteVcdAnnotation)) {
            dut => {
                dut.clock.step()
                dut.reset.poke(true.B)
                dut.clock.step()
                dut.reset.poke(false.B)

                var x = 0
                // ---- step 15 x 20 ----
                var step = 15
                dut.io.step.poke(step.S)
                dut.io.en.poke(true)
                for(i <- 0 until 20) {
                    dut.clock.step()
                    x += step
                    val y = math.floorMod(x, modu)

                dut.clock.step(20)
                dut.io.accu.expect(math.floorMod(x, modu).U)
                        // ---- step -20 x 20 ----
                step = -20
                dut.io.step.poke(step.S)
                dut.io.en.poke(true)
                for(i <- 0 until 20) {
                    dut.clock.step()
                    x += step
                    val y = math.floorMod(x, modu)
                    if(y + step < 0)
                        dut.io.wrapDown.expect(true)
                    else
                        dut.io.wrapDown.expect(false)
                    dut.io.wrapUp.expect(false)
                    dut.io.accu.expect(y.U)
                }
                println("-----------------------------")
                println(s"\u001b[1mAccumulator test Pass.\u001b[0m")
                println("=============================")
                Thread.sleep(1000)
            }
        }
    }
}

                    if(y + step >= modu)
                        dut.io.wrapUp.expect(true)
                    else
                        dut.io.wrapUp.expect(false)
                    dut.io.wrapDown.expect(false)


/*
 * 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 test_str_cordic_1

import chisel3._
import chisel3.util._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers._
import loywong._
import loywong.util._
import loywongtest._
import org.apache.commons.math3.distribution.{UniformRealDistribution, UniformIntegerDistribution}

object SimSettings {
    val nBeats = 500
    val verbose: Boolean = nBeats <= 1000

    val randomValid = true
    val validInterval = 1.0   // if random, it's the poisson lambda
    val randomReady = true
    val readyInterval = 1.0   // if random, it's the poisson lambda

    val xyWidth = 12
    val zWidth = 12

    val userWidth = 8
}

import SimSettings._

class CordicIOBitsWithUser extends CordicIOStrBits(xyWidth, zWidth) with StrHasLast with StrHasUser {
    override val last = Bool()
    override val user = UInt(userWidth.W)
}

class CordicWrapperIOBits extends StrHasData with StrHasLast with StrHasUser {
    override val data = UInt((xyWidth.up8 * 2 + zWidth.up8).W)
    override val last = Bool()
    override val user = UInt(userWidth.up8.W)
}

class CordicWrapper(xyFW: Int, zFW: Int, coor: CordicCoordinates.Value, mode: CordicMode.Value, linStgStart: Int = 0) extends Module {
    val io = IO(new Bundle {
        val us = Flipped(Irrevocable(new CordicWrapperIOBits))
        val ds = Irrevocable(new CordicWrapperIOBits)
    })

    val dut = Module(new Cordic(new CordicIOBitsWithUser, zWidth - 1, xyFW, zFW, coor, mode, linStgStart))

    io.us.bits.data.sliceByteWiseTo(dut.io.us.bits.z, dut.io.us.bits.y, dut.io.us.bits.x)
    io.us.bits.last <> dut.io.us.bits.last
    io.us.bits.user.sliceByteWiseTo(dut.io.us.bits.user)
    io.us.valid <> dut.io.us.valid
    io.us.ready <> dut.io.us.ready

    /**
     * birs for eliminate ready comb which may cause
     * "Unordered poke after peek" runtime error.
     */
    val brs = Module(new StrBirs(new CordicWrapperIOBits))

    brs.io.us.bits.data.catByteWiseFrom(dut.io.ds.bits.z, dut.io.ds.bits.y, dut.io.ds.bits.x)
    brs.io.us.bits.last <> dut.io.ds.bits.last
    brs.io.us.bits.user.catByteWiseFrom(dut.io.ds.bits.user)
    brs.io.us.valid <> dut.io.ds.valid
    brs.io.us.ready <> dut.io.ds.ready

    io.ds <> brs.io.ds
}

class test extends AnyFlatSpec with ChiselScalatestTester {

    val shouldUseVerilator =
        if (System.getProperty("os.name").toLowerCase.contains("win"))
            Seq.empty[firrtl2.annotations.Annotation]
        else
            Seq(VerilatorBackendAnnotation)

    "DUT Cordic Linear Rotation" should "Pass" in {
        /**
         * Functions for this test:
         *  - x := x
         *  - y := y + x * z
         *  - z := 0
         *
         * Domains of this test:
         * - x_i: (-1, 1)
         * - y_i: (-2, 2)
         * - z_i: (-2, 2)
         * - x_o: (-1, 1)
         * - y_o: (-4, 4)
         * - z_o: = 0
         */
        val xyFracWidth = xyWidth - 3
        val zFracWidth = zWidth - 2
        val xyLsb = math.pow(2.0, -xyFracWidth)
        val zLsb = math.pow(2.0, -zFracWidth)

        test(
            new CordicWrapper(xyFracWidth, zFracWidth, CordicCoordinates.Linear, CordicMode.Rotation)
        ).withAnnotations(
            shouldUseVerilator/* ++ Seq(WriteVcdAnnotation)*/
        ) {
            dut => {

                println(
                    s"""
                      |========================================================
                      | Test CORDIC Linear Rotation with ${nBeats} data ...""".stripMargin)

                val src = new StrTestSource(dut.clock, dut.io.us)
                src.setThrottle(randomValid, validInterval)

                val snk = new StrTestSink(dut.clock, dut.io.ds)
                snk.setThrottle(randomReady, readyInterval)

                val xRand = new UniformRealDistribution(
                    -1.0 + 16 * xyLsb, 1.0 - 16 * xyLsb
                )

                val yRand = new UniformRealDistribution(
                    -2.0 + 16 * xyLsb, 2.0 - 16 * xyLsb
                )

                val zRand = new UniformRealDistribution(
                    -2.0 + 16 * zLsb, 2.0 - 16 * zLsb
                )

                val xinSeq = Seq.fill(nBeats)(xRand.sample())
                val yinSeq = Seq.fill(nBeats)(yRand.sample())
                val zinSeq = Seq.fill(nBeats)(zRand.sample())

                val xoutRefSeq = xinSeq
                val youtRefSeq = (0 until nBeats).map(i => {
                    yinSeq(i) + xinSeq(i) * zinSeq(i)
                })
                val zoutRefSeq = Seq.fill(nBeats)(0.0)

                val txDataSeq = (0 until nBeats).map(i => {
                    CatByteWise(Seq.fill(3)(true), Seq(zWidth, xyWidth, xyWidth),
                        zinSeq(i).toFixPoint(zWidth, zFracWidth).toBigInt,
                        yinSeq(i).toFixPoint(xyWidth, xyFracWidth).toBigInt,
                        xinSeq(i).toFixPoint(xyWidth, xyFracWidth).toBigInt,
                    )
                })

                val txUserSeq = src.randomUsers(nBeats)

                val txThr = src.sendBeats(genLastAtLastBeat = true, dataSeq = txDataSeq, userSeq = txUserSeq)
                val rxThr = snk.recvBeatsToLast()

                txThr.join()
                rxThr.join()

                val outSeq = snk.dataSeq.map(d => d.bitSlicesByteWise(zWidth, xyWidth, xyWidth))
                val xoutSeq = outSeq.map(o => o(2).valueAsFixPoint(xyWidth, xyFracWidth))
                val youtSeq = outSeq.map(o => o(1).valueAsFixPoint(xyWidth, xyFracWidth))
                val zoutSeq = outSeq.map(o => o(0).valueAsFixPoint(zWidth, zFracWidth))

                if(verbose) {
                    println("--------------------------------------------------------")
                    var lastClock = 0L
                    for (i <- 0L until math.max(src.totalClocks, snk.totalClocks)) {
                        val srcStr = src.signalStringOfClock(i)
                        val snkStr = snk.signalStringOfClock(i)
                        if (srcStr(0) != ' ' || snkStr(0) != ' ') {
                            val clkInc = i - lastClock
                            println(f"clk+${clkInc}%02d=${i}%04d: ${srcStr} --> ${snkStr}")
                            lastClock = i
                        }
                    }
                    println("--------------------------------------------------------")
                    for (i <- 0 until nBeats) {
                        print(f"beat_${i}%04d: ")
                        print(f"(${xinSeq(i)}%6.3f, ${yinSeq(i)}%6.3f, ${zinSeq(i)}%6.3f) --> ")
                        println(f"(${xoutRefSeq(i)}%6.3f, ${youtRefSeq(i)}%6.3f, ${zoutRefSeq(i)}%6.3f)")
                        print("                                    ")
                        println(f"got (${xoutSeq(i)}%6.3f, ${youtSeq(i)}%6.3f, ${zoutSeq(i)}%6.3f)")
                        println()
                    }
                }

                println("--------------------------------------------------------")
                val xerr = elemWiseDiff(xoutSeq, xoutRefSeq)
                val yerr = elemWiseDiff(youtSeq, youtRefSeq)
                val zerr = elemWiseDiff(zoutSeq, zoutRefSeq)
                val (xm, xmi, xr) = maxAndRms(xerr)
                val (ym, ymi, yr) = maxAndRms(yerr)
                val (zm, zmi, zr) = maxAndRms(zerr)
                val (xmLsb, xrLsb) = (xm / xyLsb, xr / xyLsb)
                val (ymLsb, yrLsb) = (ym / xyLsb, yr / xyLsb)
                val (zmLsb, zrLsb) = (zm / zLsb, zr / zLsb)

                println(f"Error of x: Max Abs = ${xm}%8.6f(${xmLsb}%4.2fLSB, @${xmi}%03d), Std Dev = ${xr}%8.6f(${xrLsb}%4.2fLSB)")
                println(f"Error of y: Max Abs = ${ym}%8.6f(${ymLsb}%4.2fLSB, @${ymi}%03d), Std Dev = ${yr}%8.6f(${yrLsb}%4.2fLSB)")
                println(f"Error of z: Max Abs = ${zm}%8.6f(${zmLsb}%4.2fLSB, @${zmi}%03d), Std Dev = ${zr}%8.6f(${zrLsb}%4.2fLSB)")

                val inMaxAbsLimit = (xmLsb < 2 && ymLsb < 20 && zmLsb < 10)
                inMaxAbsLimit should be (true)
                val inStdDevLimit = (xrLsb < 1 && yrLsb < 4 && zrLsb < 2)
                inStdDevLimit should be (true)

                println("--------------------------------------------------------")
                println(s"\u001b[1mErrors Meets Spec: ${if (inMaxAbsLimit && inStdDevLimit) "YES" else "NO"}\u001b[0m")
                println("========================================================")
                Thread.sleep(1000)
            }
        }
    }

    "DUT Cordic Linear Vectoring" should "PASS" in {
        /**
         * Functions for this test:
         *  - x := x
         *  - y := 0
         *  - z := z + y / x
         *
         * Domains of this test:
         * - x_i: (-1, 1), except [-1/8, 1/8]
         * - y_i: (-1, 1)
         * - z_i: (-8, 8)
         * - x_o: (-1, 1)
         * - y_o = 0
         * - z_o: (-16, 16)
         */
        val xyFracWidth = xyWidth - 3
        val zFracWidth = zWidth - 5
        val xyLsb = math.pow(2.0, -xyFracWidth)
        val zLsb = math.pow(2.0, -zFracWidth)

        test(
            // stage Idx start from -2, for extend z range from abs(y/x) < 2 to abs(y/x) < 8,
            // meanwhile, x & y width must cover max{x_in, y_in} * 2^2, x_out, y_out}
            new CordicWrapper(xyFracWidth, zFracWidth, CordicCoordinates.Linear, CordicMode.Vectoring, -2)
        ).withAnnotations(
            shouldUseVerilator/* ++ Seq(WriteVcdAnnotation)*/
        ) {
            dut => {

                println(
                    s"""
                      |========================================================
                      | Test CORDIC Linear Vectoring with ${nBeats} data ...""".stripMargin)

                val src = new StrTestSource(dut.clock, dut.io.us)
                src.setThrottle(randomValid, validInterval)

                val snk = new StrTestSink(dut.clock, dut.io.ds)
                snk.setThrottle(randomReady, readyInterval)

                val xAbsRand = new UniformRealDistribution(
                    1.0 / 8, 1.0 - 16 * xyLsb
                )
                val xSignRand = new UniformIntegerDistribution(0, 1)

                val yRand = new UniformRealDistribution(
                    -1.0 + 16 * xyLsb, 1.0 - 16 * xyLsb
                )

                val zRand = new UniformRealDistribution(
                    -8.0 + 16 * zLsb, 8.0 - 16 * zLsb
                )

                val xinSeq = Seq.fill(nBeats)(xAbsRand.sample() * (if (xSignRand.sample() == 1) 1.0 else -1.0))
                val yinSeq = Seq.fill(nBeats)(yRand.sample())
                val zinSeq = Seq.fill(nBeats)(zRand.sample())

                val xoutRefSeq = xinSeq.map(_.abs)
                val youtRefSeq = Seq.fill(nBeats)(0.0)
                val zoutRefSeq = (0 until nBeats).map(i => {
                    zinSeq(i) + yinSeq(i) / xinSeq(i)
                })

                val txDataSeq = (0 until nBeats).map(i => {
                    CatByteWise(Seq.fill(3)(true), Seq(zWidth, xyWidth, xyWidth),
                        zinSeq(i).toFixPoint(zWidth, zFracWidth).toBigInt,
                        yinSeq(i).toFixPoint(xyWidth, xyFracWidth).toBigInt,
                        xinSeq(i).toFixPoint(xyWidth, xyFracWidth).toBigInt,
                    )
                })

                val txUserSeq = src.randomUsers(nBeats)

                val txThr = src.sendBeats(genLastAtLastBeat = true, dataSeq = txDataSeq, userSeq = txUserSeq)
                val rxThr = snk.recvBeatsToLast()

                txThr.join()
                rxThr.join()

                val outSeq = snk.dataSeq.map(d => d.bitSlicesByteWise(zWidth, xyWidth, xyWidth))
                val xoutSeq = outSeq.map(o => o(2).valueAsFixPoint(xyWidth, xyFracWidth))
                val youtSeq = outSeq.map(o => o(1).valueAsFixPoint(xyWidth, xyFracWidth))
                val zoutSeq = outSeq.map(o => o(0).valueAsFixPoint(zWidth, zFracWidth))

                if(verbose) {
                    println("--------------------------------------------------------")
                    var lastClock = 0L
                    for (i <- 0L until math.max(src.totalClocks, snk.totalClocks)) {
                        val srcStr = src.signalStringOfClock(i)
                        val snkStr = snk.signalStringOfClock(i)
                        if (srcStr(0) != ' ' || snkStr(0) != ' ') {
                            val clkInc = i - lastClock
                            println(f"clk+${clkInc}%02d=${i}%04d: ${srcStr} --> ${snkStr}")
                            lastClock = i
                        }
                    }
                    println("--------------------------------------------------------")
                    for (i <- 0 until nBeats) {

                        print(f"(${xinSeq(i)}%6.3f, ${yinSeq(i)}%6.3f, ${zinSeq(i)}%6.3f) --> ")
                        println(f"(${xoutRefSeq(i)}%6.3f, ${youtRefSeq(i)}%6.3f, ${zoutRefSeq(i)}%7.3f)")
                        print("                                    ")
                        println(f"got (${xoutSeq(i)}%6.3f, ${youtSeq(i)}%6.3f, ${zoutSeq(i)}%7.3f)")
                        println()
                    }
                }

                println("--------------------------------------------------------")
                val xerr = elemWiseDiff(xoutSeq, xoutRefSeq)
                val yerr = elemWiseDiff(youtSeq, youtRefSeq)
                val zerr = elemWiseDiff(zoutSeq, zoutRefSeq)
                val (xm, xmi, xr) = maxAndRms(xerr)
                val (ym, ymi, yr) = maxAndRms(yerr)
                val (zm, zmi, zr) = maxAndRms(zerr)
                val (xmLsb, xrLsb) = (xm / xyLsb, xr / xyLsb)
                val (ymLsb, yrLsb) = (ym / xyLsb, yr / xyLsb)
                val (zmLsb, zrLsb) = (zm / zLsb, zr / zLsb)

                println(f"Error of x: Max Abs = ${xm}%8.6f(${xmLsb}%4.2fLSB, @${xmi}%03d), Std Dev = ${xr}%8.6f(${xrLsb}%4.2fLSB)")
                println(f"Error of y: Max Abs = ${ym}%8.6f(${ymLsb}%4.2fLSB, @${ymi}%03d), Std Dev = ${yr}%8.6f(${yrLsb}%4.2fLSB)")
                println(f"Error of z: Max Abs = ${zm}%8.6f(${zmLsb}%4.2fLSB, @${zmi}%03d), Std Dev = ${zr}%8.6f(${zrLsb}%4.2fLSB)")

                val inMaxAbsLimit = (xmLsb < 2 && ymLsb < 10 && zmLsb < 20)
                inMaxAbsLimit should be (true)
                val inStdDevLimit = (xrLsb < 1 && yrLsb < 2 && zrLsb < 4)
                inStdDevLimit should be (true)

                println("--------------------------------------------------------")
                println(s"\u001b[1mErrors Meets Spec: ${if (inMaxAbsLimit && inStdDevLimit) "YES" else "NO"}\u001b[0m")
                println("========================================================")
                Thread.sleep(1000)
            }
        }
    }
}


/*
 * 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 test_str_cordic_2

import chisel3._
import chisel3.util._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers._
import loywong._
import loywong.util._
import loywongtest._
import org.apache.commons.math3.distribution.{UniformRealDistribution, UniformIntegerDistribution}

object SimSettings {
    val nBeats = 500
    val verbose: Boolean = nBeats <= 1000

    val randomValid = false
    val validInterval = 0.0   // if random, it's the poisson lambda
    val randomReady = false
    val readyInterval = 0.0   // if random, it's the poisson lambda

    val xyWidth = 12
    val zWidth = 12

    val userWidth = 8
}

import SimSettings._

class CordicIOBitsWithUser extends CordicIOStrBits(xyWidth, zWidth) with StrHasLast with StrHasUser {
    override val last = Bool()
    override val user = UInt(userWidth.W)
}

class CordicWrapperIOBits extends StrHasData with StrHasLast with StrHasUser {
    override val data = UInt((xyWidth.up8 * 2 + zWidth.up8).W)
    override val last = Bool()
    override val user = UInt(userWidth.up8.W)
}

class CordicWrapper(xyFW: Int, zFW: Int, coor: CordicCoordinates.Value, mode: CordicMode.Value, linStgStart: Int = 0) extends Module {
    val io = IO(new Bundle {
        val us = Flipped(Irrevocable(new CordicWrapperIOBits))
        val ds = Irrevocable(new CordicWrapperIOBits)
    })

    val dut = Module(new Cordic(new CordicIOBitsWithUser, zWidth - 1, xyFW, zFW, coor, mode, linStgStart))

    io.us.bits.data.sliceByteWiseTo(dut.io.us.bits.z, dut.io.us.bits.y, dut.io.us.bits.x)
    io.us.bits.last <> dut.io.us.bits.last
    io.us.bits.user.sliceByteWiseTo(dut.io.us.bits.user)
    io.us.valid <> dut.io.us.valid
    io.us.ready <> dut.io.us.ready

    /**
     * birs for eliminate ready comb which may cause
     * "Unordered poke after peek" runtime error.
     */
    val brs = Module(new StrBirs(new CordicWrapperIOBits))

    brs.io.us.bits.data.catByteWiseFrom(dut.io.ds.bits.z, dut.io.ds.bits.y, dut.io.ds.bits.x)
    brs.io.us.bits.last <> dut.io.ds.bits.last
    brs.io.us.bits.user.catByteWiseFrom(dut.io.ds.bits.user)
    brs.io.us.valid <> dut.io.ds.valid
    brs.io.us.ready <> dut.io.ds.ready

    io.ds <> brs.io.ds
}

class test extends AnyFlatSpec with ChiselScalatestTester {

    val shouldUseVerilator =
        if (System.getProperty("os.name").toLowerCase.contains("win"))
            Seq.empty[firrtl2.annotations.Annotation]
        else
            Seq(VerilatorBackendAnnotation)

    "DUT Cordic Hyperbolic Rotation" should "Pass" in {
        /**
         * Functions for this test:
         *  - x := x cosh(z) + y sinh(z) = 0.5 * (x + y) exp(z) + 0.5 * (x - y) exp(z)
         *  - y := x sinh(z) + y cosh(z) = 0.5 * (y + x) exp(z) + 0.5 * (y - x) exp(z)
         *  - z := 0
         *
         * Domains of this test:
         * - x_i: (-4/e, 4/e)
         * - y_i: (-4/e, 4/e)
         * - z_i: (-1, 1)
         * - x_o: (-4, 4)
         * - y_o: (-4, 4)
         * - z_o: = 0
         */
        val xyFracWidth = xyWidth - 3
        val zFracWidth = zWidth - 1
        val xyLsb = math.pow(2.0, -xyFracWidth)
        val zLsb = math.pow(2.0, -zFracWidth)

        test(
            new CordicWrapper(xyFracWidth, zFracWidth, CordicCoordinates.Hyperbolic, CordicMode.Rotation)
        ).withAnnotations(
            shouldUseVerilator/* ++ Seq(WriteVcdAnnotation)*/
        ) {
            dut => {

                println(
                    s"""
                      |========================================================
                      | Test CORDIC Hyperbolic Rotation with ${nBeats} data ...""".stripMargin)

                val src = new StrTestSource(dut.clock, dut.io.us)
                src.setThrottle(randomValid, validInterval)

                val snk = new StrTestSink(dut.clock, dut.io.ds)
                snk.setThrottle(randomReady, readyInterval)

                val xyRand = new UniformRealDistribution(
                    -4.0 / math.E + 16 * xyLsb, 4.0 / math.E - 16 * xyLsb
                )

                val zRand = new UniformRealDistribution(
                    -1.0 + 16 * zLsb, 1.0 - 16 * zLsb
                )

                val xinSeq = Seq.fill(nBeats)(xyRand.sample())
                val yinSeq = Seq.fill(nBeats)(xyRand.sample())
                val zinSeq = Seq.fill(nBeats)(zRand.sample())

                val xoutRefSeq = (0 until nBeats).map(i => {
                    xinSeq(i) * math.cosh(zinSeq(i)) + yinSeq(i) * math.sinh(zinSeq(i))
                })
                val youtRefSeq = (0 until nBeats).map(i => {
                    xinSeq(i) * math.sinh(zinSeq(i)) + yinSeq(i) * math.cosh(zinSeq(i))
                })
                val zoutRefSeq = Seq.fill(nBeats)(0.0)

                val txDataSeq = (0 until nBeats).map(i => {
                    CatByteWise(Seq.fill(3)(true), Seq(zWidth, xyWidth, xyWidth),
                        zinSeq(i).toFixPoint(zWidth, zFracWidth).toBigInt,
                        yinSeq(i).toFixPoint(xyWidth, xyFracWidth).toBigInt,
                        xinSeq(i).toFixPoint(xyWidth, xyFracWidth).toBigInt,
                    )
                })

                val txUserSeq = src.randomUsers(nBeats)

                val txThr = src.sendBeats(genLastAtLastBeat = true, dataSeq = txDataSeq, userSeq = txUserSeq)
                val rxThr = snk.recvBeatsToLast()

                txThr.join()
                rxThr.join()

                val outSeq = snk.dataSeq.map(d => d.bitSlicesByteWise(zWidth, xyWidth, xyWidth))
                val xoutSeq = outSeq.map(o => o(2).valueAsFixPoint(xyWidth, xyFracWidth))
                val youtSeq = outSeq.map(o => o(1).valueAsFixPoint(xyWidth, xyFracWidth))
                val zoutSeq = outSeq.map(o => o(0).valueAsFixPoint(zWidth, zFracWidth))

                if(verbose) {
                    println("--------------------------------------------------------")
                    var lastClock = 0L
                    for (i <- 0L until math.max(src.totalClocks, snk.totalClocks)) {
                        val srcStr = src.signalStringOfClock(i)
                        val snkStr = snk.signalStringOfClock(i)
                        if (srcStr(0) != ' ' || snkStr(0) != ' ') {
                            val clkInc = i - lastClock
                            println(f"clk+${clkInc}%02d=${i}%04d: ${srcStr} --> ${snkStr}")
                            lastClock = i
                        }
                    }
                    println("--------------------------------------------------------")
                    for (i <- 0 until nBeats) {

                        print(f"(${xinSeq(i)}%6.3f, ${yinSeq(i)}%6.3f, ${zinSeq(i)}%6.3f) --> ")
                        println(f"(${xoutRefSeq(i)}%6.3f, ${youtRefSeq(i)}%6.3f, ${zoutRefSeq(i)}%6.3f)")
                        print("                                    ")
                        println(f"got (${xoutSeq(i)}%6.3f, ${youtSeq(i)}%6.3f, ${zoutSeq(i)}%6.3f)")
                        println()
                    }
                }

                println("--------------------------------------------------------")
                val xerr = elemWiseDiff(xoutSeq, xoutRefSeq)
                val yerr = elemWiseDiff(youtSeq, youtRefSeq)
                val zerr = elemWiseDiff(zoutSeq, zoutRefSeq)
                val (xm, xmi, xr) = maxAndRms(xerr)
                val (ym, ymi, yr) = maxAndRms(yerr)
                val (zm, zmi, zr) = maxAndRms(zerr)
                val (xmLsb, xrLsb) = (xm / xyLsb, xr / xyLsb)
                val (ymLsb, yrLsb) = (ym / xyLsb, yr / xyLsb)
                val (zmLsb, zrLsb) = (zm / zLsb, zr / zLsb)

                println(f"Error of x: Max Abs = ${xm}%8.6f(${xmLsb}%4.2fLSB, @${xmi}%03d), Std Dev = ${xr}%8.6f(${xrLsb}%4.2fLSB)")
                println(f"Error of y: Max Abs = ${ym}%8.6f(${ymLsb}%4.2fLSB, @${ymi}%03d), Std Dev = ${yr}%8.6f(${yrLsb}%4.2fLSB)")
                println(f"Error of z: Max Abs = ${zm}%8.6f(${zmLsb}%4.2fLSB, @${zmi}%03d), Std Dev = ${zr}%8.6f(${zrLsb}%4.2fLSB)")

                val inMaxAbsLimit = (xmLsb < 20 && ymLsb < 20 && zmLsb < 10)
                inMaxAbsLimit should be (true)
                val inStdDevLimit = (xrLsb < 4 && yrLsb < 4 && zrLsb < 2)
                inStdDevLimit should be (true)

                println("--------------------------------------------------------")
                println(s"\u001b[1mErrors Meets Spec: ${if (inMaxAbsLimit && inStdDevLimit) "YES" else "NO"}\u001b[0m")
                println("========================================================")
                Thread.sleep(1000)
            }
        }
    }

    "DUT Cordic Hyperbolic Vectoring" should "PASS" in {
        /**
         * Functions for this test:
         *  - x := \sqrt{x^2 - y^2}
         *  - y := 0
         *  - z := z + arctanh(y / x) = z + 0.5 * ln((x + y) / (x - y))
         *
         * Domains of this test:
         * - x_i: (-1, 1)
         * - y_i: (-1, 1) and abs(y) < abs(x) * 0.761594 (make abs(arctanh(y/x) < 1)
         * - z_i: (-1, 1)
         * - x_o: (0, 1)
         * - y_o = 0
         * - z_o: (-2, 2)
         */
        val xyFracWidth = xyWidth - 1
        val zFracWidth = zWidth - 2
        val xyLsb = math.pow(2.0, -xyFracWidth)
        val zLsb = math.pow(2.0, -zFracWidth)

        test(
            new CordicWrapper(xyFracWidth, zFracWidth, CordicCoordinates.Hyperbolic, CordicMode.Vectoring)
        ).withAnnotations(
            shouldUseVerilator/* ++ Seq(WriteVcdAnnotation)*/
        ) {
            dut => {

                println(
                    s"""
                      |========================================================
                      | Test CORDIC Hyperbolic Vectoring with ${nBeats} data ...""".stripMargin)

                val src = new StrTestSource(dut.clock, dut.io.us)
                src.setThrottle(randomValid, validInterval)

                val snk = new StrTestSink(dut.clock, dut.io.ds)
                snk.setThrottle(randomReady, readyInterval)

                val xyAbsRand = new UniformRealDistribution(
                    0.25 + 16 * xyLsb, 1.0 - 16 * xyLsb
                )
                val xySignRand = new UniformIntegerDistribution(0, 1)

                val zRand = new UniformRealDistribution(
                    -1.0 + 16 * zLsb, 1.0 - 16 * zLsb
                )

                val xinSeq = Seq.fill(nBeats)(xyAbsRand.sample() * (if (xySignRand.sample() == 1) 1.0 else -1.0))
                val yinSeq = xinSeq.map(x =>
                    xyAbsRand.sample().mapFromTo(0, 1, 0.25 * 0.761594, x * 0.761594) * (if (xySignRand.sample() == 1) 1.0 else -1.0))
                val zinSeq = Seq.fill(nBeats)(zRand.sample())

                val xoutRefSeq = (0 until nBeats).map(i =>
                    math.sqrt(xinSeq(i) * xinSeq(i) - yinSeq(i) * yinSeq(i))
                )
                val youtRefSeq = Seq.fill(nBeats)(0.0)
                val zoutRefSeq = (0 until nBeats).map(i => {
                    zinSeq(i) + atanh(yinSeq(i) / xinSeq(i))
                })

                val txDataSeq = (0 until nBeats).map(i => {
                    CatByteWise(Seq.fill(3)(true), Seq(zWidth, xyWidth, xyWidth),
                        zinSeq(i).toFixPoint(zWidth, zFracWidth).toBigInt,
                        yinSeq(i).toFixPoint(xyWidth, xyFracWidth).toBigInt,
                        xinSeq(i).toFixPoint(xyWidth, xyFracWidth).toBigInt,
                    )
                })

                val txUserSeq = src.randomUsers(nBeats)

                val txThr = src.sendBeats(genLastAtLastBeat = true, dataSeq = txDataSeq, userSeq = txUserSeq)
                val rxThr = snk.recvBeatsToLast()

                txThr.join()
                rxThr.join()

                val outSeq = snk.dataSeq.map(d => d.bitSlicesByteWise(zWidth, xyWidth, xyWidth))
                val xoutSeq = outSeq.map(o => o(2).valueAsFixPoint(xyWidth, xyFracWidth))
                val youtSeq = outSeq.map(o => o(1).valueAsFixPoint(xyWidth, xyFracWidth))
                val zoutSeq = outSeq.map(o => o(0).valueAsFixPoint(zWidth, zFracWidth))

                if(verbose) {
                    println("--------------------------------------------------------")
                    var lastClock = 0L
                    for (i <- 0L until math.max(src.totalClocks, snk.totalClocks)) {
                        val srcStr = src.signalStringOfClock(i)
                        val snkStr = snk.signalStringOfClock(i)
                        if (srcStr(0) != ' ' || snkStr(0) != ' ') {
                            val clkInc = i - lastClock
                            println(f"clk+${clkInc}%02d=${i}%04d: ${srcStr} --> ${snkStr}")
                            lastClock = i
                        }
                    }
                    println("--------------------------------------------------------")
                    for (i <- 0 until nBeats) {
                        print(f"beat_${i}%04d: ")
                        print(f"(${xinSeq(i)}%6.3f, ${yinSeq(i)}%6.3f, ${zinSeq(i)}%6.3f) --> ")
                        println(f"(${xoutRefSeq(i)}%6.3f, ${youtRefSeq(i)}%6.3f, ${zoutRefSeq(i)}%7.3f)")
                        print("                                    ")
                        println(f"got (${xoutSeq(i)}%6.3f, ${youtSeq(i)}%6.3f, ${zoutSeq(i)}%7.3f)")
                        println()
                    }
                }

                println("--------------------------------------------------------")
                val xerr = elemWiseDiff(xoutSeq, xoutRefSeq)
                val yerr = elemWiseDiff(youtSeq, youtRefSeq)
                val zerr = elemWiseDiff(zoutSeq, zoutRefSeq)
                val (xm, xmi, xr) = maxAndRms(xerr)
                val (ym, ymi, yr) = maxAndRms(yerr)
                val (zm, zmi, zr) = maxAndRms(zerr)
                val (xmLsb, xrLsb) = (xm / xyLsb, xr / xyLsb)
                val (ymLsb, yrLsb) = (ym / xyLsb, yr / xyLsb)
                val (zmLsb, zrLsb) = (zm / zLsb, zr / zLsb)

                println(f"Error of x: Max Abs = ${xm}%8.6f(${xmLsb}%4.2fLSB, @${xmi}%03d), Std Dev = ${xr}%8.6f(${xrLsb}%4.2fLSB)")
                println(f"Er ror of y: Max Abs = ${ym}%8.6f(${ymLsb}%4.2fLSB, @${ymi}%03d), Std Dev = ${yr}%8.6f(${yrLsb}%4.2fLSB)")
                println(f"Error of z: Max Abs = ${zm}%8.6f(${zmLsb}%4.2fLSB, @${zmi}%03d), Std Dev = ${zr}%8.6f(${zrLsb}%4.2fLSB)")

                val inMaxAbsLimit = (xmLsb < 20 && ymLsb < 10 && zmLsb < 20)
                inMaxAbsLimit should be (true)
                val inStdDevLimit = (xrLsb < 4 && yrLsb < 2 && zrLsb < 4)
                inStdDevLimit should be (true)

                println("--------------------------------------------------------")
                println(s"\u001b[1mErrors Meets Spec: ${if (inMaxAbsLimit && inStdDevLimit) "YES" else "NO"}\u001b[0m")
                println("========================================================")
                Thread.sleep(1000)
            }
        }
    }
}


package rself

import chisel3._
import chisel3.util._

import ALU_MODE._

class ALUIO extends Bundle {

  val B = Input(UInt(32.W))
  val alu_op = Input(UInt(4.W))
  val out = Output(UInt(32.W))
}

class ALU extends Module {
  val io = IO(new ALUIO)
  val shamt = io.B(4,0).asUInt

  io.out := MuxLookup(io.alu_op, io.B, Seq(
    ALU_ADD  -> (io.A + io.B),
    ALU_SUB  -> (io.A - io.B),
    ALU_SRA  -> (io.A.asSInt >> shamt).asUInt,
    ALU_SRL  -> (io.A >> shamt),
    ALU_SLL  -> (io.A << shamt),
    ALU_SLT  -> (io.A.asSInt < io.B.asSInt),
    ALU_SLTU -> (io.A < io.B),
    ALU_AND  -> (io.A & io.B),
    ALU_OR   -> (io.A | io.B),
    ALU_XOR  -> (io.A ^ io.B)))
}


package rself

import chisel3._
import chisel3.util._

import BR_TYPE._

class BrCondIO extends Bundle {
  val rs1 = Input(UInt(32.W))
  val rs2 = Input(UInt(32.W))
  val br_type = Input(UInt(3.W))
}

    val cond = new BrCondIO
    val isJmp = Output(Bool())
  })
  val xlen = 32
  val diff = io.cond.rs1 - io.cond.rs2
  val neq  = diff.orR
  val eq   = !neq
  val isSameSign = io.cond.rs1(xlen-1) === io.cond.rs2(xlen-1)
  val lt   = Mux(isSameSign, diff(xlen-1), io.cond.rs1(xlen-1))
  val ltu  = Mux(isSameSign, diff(xlen-1), io.cond.rs2(xlen-1))
  val ge   = !lt
  val geu  = !ltu

  io.isJmp := MuxLookup(io.cond.br_type, false.B, Seq(
    BR_EQ  -> eq,
    BR_NE  -> neq,
    BR_LT  -> lt,
    BR_GE  -> ge,
    BR_LTU -> ltu,
    BR_GEU -> geu))
}

class BrJump extends Module {
  val io = IO(new Bundle {
    val jmpAddr = Output(UInt(32.W))
    val jmp = new BrJumpIO
  })
  io.jmpAddr := io.jmp.PC + Utility.extendSign(io.jmp.disp, 32)
}

class Branch extends Module {
  val io = IO(new Bundle {
    val cond = new BrCondIO
    val jump = new BrJumpIO
    val jmpAddr = Output(UInt(32.W))
    val isJmp = Output(Bool())
  })
  val modCond = Module(new BrCond)
  val modJump = Module(new BrJump)
  modCond.io.cond := io.cond
  modJump.io.jmp := io.jump

  io.isJmp   := modCond.io.isJmp
  io.jmpAddr := modJump.io.jmpAddr
}

class BrJumpIO extends Bundle {
  val disp = Input(UInt(13.W))
  val PC = Input(UInt(32.W))
}


package rself

import java.io.{File, FileWriter}

object Compile {
  val modules:Map[String, () => chisel3.Module] = Map(
    "RegFile"   -> (() => new RegFile),
    "ALU"       -> (() => new ALU),
    "Control"   -> (() => new Control),
    "ImmGen"    -> (() => new ImmGen),
    "Branch"    -> (() => new Branch),
    "LoadStore" -> (() => new LoadStore),
    "Execute"   -> (() => new Execute),
    "Datapath"  -> (() => new Datapath)
  )

  def dump(dir:File, mod: () => chisel3.Module) = {
    val chirrtl = firrtl.Parser.parse(chisel3.Driver.emit(mod))
    val writer = new FileWriter(new File(dir, s"${chirrtl.main}.fir"))
    writer write chirrtl.serialize
    writer.close

    new firrtl.VerilogCompiler compile (
      firrtl.CircuitState(chirrtl, firrtl.ChirrtlForm), verilog)
    verilog.close
  }

  def main(args: Array[String]): Unit = {
    val dir = new File(args(0)) ; dir.mkdirs
    if (args.length >= 2) {
      modules.get(args(1)) match {
        case Some(mod) =>
          dump(dir, mod)
        case _ =>
          println(s"no module names ${args(1)}")
      }
    }
    else {
      modules.foreach {
        case (key, value) => dump(dir, value)
      }
    }
  }
}

val verilog = new FileWriter(new File(dir, s"${chirrtl.main}.v"))


// auto generate by python scripts

package rself

import chisel3._

object ALU_MODE {
  val ALU_ADD  = 0.U(4.W)
  val ALU_SUB  = 8.U(4.W)
  val ALU_SLL  = 1.U(4.W)
  val ALU_SLT  = 2.U(4.W)
  val ALU_SLTU = 3.U(4.W)
  val ALU_XOR  = 4.U(4.W)
  val ALU_SRL  = 5.U(4.W)

}

object IMM_MODE {
  val IMM_R = 0.U(3.W)
  val IMM_I = 1.U(3.W)
  val IMM_S = 2.U(3.W)
  val IMM_U = 3.U(3.W)
  val IMM_J = 4.U(3.W)
  val IMM_B = 5.U(3.W)
  val IMM_Z = 6.U(3.W)
}

object SL_TYPE {
  val SL_B = 0.U(2.W)
  val SL_H = 1.U(2.W)
  val SL_W = 2.U(2.W)
  val SL_D = 3.U(2.W)
}

object BR_TYPE {
  val BR_EQ  = 0.U(3.W)
  val BR_NE  = 1.U(3.W)
  val BR_LT  = 4.U(3.W)
  val BR_GE  = 5.U(3.W)
  val BR_LTU = 6.U(3.W)
  val BR_GEU = 7.U(3.W)
}

  val ALU_SRA  = 13.U(4.W)
  val ALU_OR   = 6.U(4.W)


package rself

import chisel3._
import chisel3.util._

object ACT {
  val ALU = 0.U(3.W) // ALU
  val BRC = 1.U(3.W) // BranchCondition
  val LDS = 2.U(3.W) // Load and Store
  val FEN = 3.U(3.W) // Fence
  val SYS = 4.U(3.W) // System call
  val LUI = 5.U(3.W) // Load Upper Immediate
  val AUI = 6.U(3.W) // Add Upper Immediate to PC
  val JMP = 7.U(3.W) // Jump Directly
}

import IMM_MODE._

object Control {
  val opdefault = List(IMM_Z, ACT.SYS)
  val opsel = Array(
    BitPat("b01100") -> List(IMM_R, ACT.ALU),
    BitPat("b00100") -> List(IMM_I, ACT.ALU),
    BitPat("b01000") -> List(IMM_S, ACT.LDS),
    BitPat("b00000") -> List(IMM_I, ACT.LDS),
    BitPat("b11000") -> List(IMM_B, ACT.BRC),
    BitPat("b11100") -> List(IMM_Z, ACT.SYS),
    BitPat("b00011") -> List(IMM_Z, ACT.FEN),
    BitPat("b11011") -> List(IMM_J, ACT.JMP),
    BitPat("b11001") -> List(IMM_I, ACT.JMP),
    BitPat("b00101") -> List(IMM_U, ACT.AUI),
    BitPat("b01101") -> List(IMM_U, ACT.LUI))
}

class Control extends Module {
  val io = IO(new Bundle {

  io.sel := ctrlSignals(1)
  io.mode := MuxLookup(ctrlSignals(1), 0.U(4.W), Seq(
    ACT.ALU -> Mux(io.inst(5) === 1.U,
      Cat(io.inst(30), io.inst(14, 12)),
      Mux(io.inst(13, 12) === 1.U(2.W),
        Cat(io.inst(30), io.inst(14, 12)),
        Cat(0.U(1.W), io.inst(14, 12)))),
    ACT.LDS -> Cat(io.inst(5), io.inst(14, 12)),
    ACT.BRC -> Cat(0.U(1.W), io.inst(14, 12)),
    ACT.JMP -> Cat(0.U(3.W), io.inst(3))))
}

    val inst = Input(UInt(32.W))
    val mode = Output(UInt(4.W))
    val sel  = Output(UInt(3.W))
    val imm  = Output(UInt(3.W))


package rself

import chisel3._
import chisel3.util._

object Const {
  val PC_START = 0x200
  val PC_EVEC  = 0x100
}

object Instruction {
  def NOP = BitPat.bitPatToUInt(BitPat("b00000000000000000000000000010011"))
}

class InstructionReader extends Module {
  val io = IO(new Bundle {
    val en    = Input(Bool())
    val ready = Output(Bool())

    val isJmp = Input(Bool())
    val jaddr = Input(UInt(32.W))

    val inst  = Output(UInt(32.W))
    val pc    = Output(UInt(32.W))

    val mem = Flipped(new MemoryReaderIO)
  })
  val pc  = RegInit(Const.PC_START.U(32.W))
  val npc = RegInit(Const.PC_START.U(32.W))
  val inst = RegInit(Instruction.NOP)

  val vaild = RegInit(false.B)

  val posedgeState = RegInit(false.B)
  io.mem.ren := io.en | posedgeState
  posedgeState := (io.en | posedgeState) & ~io.mem.vaild

  when (reset.toBool) {
    pc := Const.PC_START.U(32.W)
  } .elsewhen (io.en) {
    pc := Mux(io.isJmp, io.jaddr, npc)
    vaild := false.B
  }

  when (io.mem.vaild & ~vaild) {
    inst := io.mem.data
    io.ready := true.B
    npc := pc + 4.U
    vaild := true.B
  } .otherwise {
    io.ready := false.B
  }

  io.mem.addr := pc
  io.inst := inst
  io.pc := pc
}

class WriteBack extends Module {
  val io = IO(new Bundle {
    val en    = Input(Bool())
    val ready = Output(Bool())

    val rd   = Input(UInt(5.W))
    val data = Input(UInt(32.W))

    val reg = Flipped(new RegFileWriterIO)
  })

  io.reg.waddr := io.rd
  io.reg.wdata := io.data
  io.reg.wen := io.en

  when (io.en) {
    io.ready := true.B
  }.otherwise {
    io.ready := false.B
  }
}

class DatapathIO extends Bundle {
  val dmem = Flipped(new MemoryIO)
  val imem = Flipped(new MemoryReaderIO)
  val sys  = Output(Bool())
}

class Datapath extends Module {
  val io  = IO(new DatapathIO)
  val reg = Module(new RegFile)
  val exe = Module(new Execute)
  val inr = Module(new InstructionReader)
  val wrb = Module(new WriteBack)

  exe.io.inst := inr.io.inst
  exe.io.pc   := inr.io.pc

  inr.io.isJmp := exe.io.jmp
  inr.io.jaddr := exe.io.npc

  wrb.io.rd   := exe.io.rd
  wrb.io.data := exe.io.data

  exe.io.mem <> io.dmem
  inr.io.mem <> io.imem
  exe.io.reg <> reg.io.reader
  wrb.io.reg <> reg.io.writer

  io.sys := exe.io.sys

  val inrEnable = RegInit(true.B)
  val exeEnable = RegInit(false.B)
  val wrbEnable = RegInit(false.B)

  val mods = Array(
    inr.io.en -> inr.io.ready,
    exe.io.en -> exe.io.ready,
    wrb.io.en -> wrb.io.ready
  )

  val enReg = mods.indices map { i => RegInit((i == 0).B) }

  val modsize = mods.size

  mods.zipWithIndex foreach { module =>
    val ((en, ready), index) = module
    en := enReg(index)
    rdReg(index) := ready
    enReg((index+1) % modsize) := ready & ~rdReg(index)
  }
}

val rdReg = mods.indices map { i => RegInit(false.B) }


package rself

import chisel3._
import chisel3.util._

class ExecuteIO extends Bundle {
  val en    = Input(Bool())
  val pc    = Input(UInt(32.W))
  val ready = Output(Bool())

  val inst  = Input(UInt(32.W))

  val jmp   = Output(Bool())
  val npc   = Output(UInt(32.W))
  val rd    = Output(UInt(5.W))
  val data  = Output(UInt(32.W))
  val sys   = Output(Bool())

  val mem = Flipped(new MemoryIO)

}

class Execute extends Module {
  val io = IO(new ExecuteIO)

  val ctrl = Module(new Control)
  val immg = Module(new ImmGen)

  // Components
  val alu = Module(new ALU)
  val brc = Module(new Branch)
  val jmp = Module(new Jump)
  val lds = Module(new LoadStore)
  val aui = Module(new AddUpperImmPC)
  val lui = Module(new LoadUpperImm)
  val fen = Module(new MemoryOperation)
  val sys = Module(new SystemOperation)

  ctrl.io.inst := io.inst

  io.reg.raddr1 := io.inst(19, 15)
  io.reg.raddr2 := io.inst(24, 20)

  immg.io.inst := io.inst
  immg.io.mode := ctrl.io.imm

  val rs1 = io.reg.rdata1
  val rs2 = io.reg.rdata2
  val imm = immg.io.value
  val mode = ctrl.io.mode

  alu.io.A := rs1
  alu.io.B := Mux(ctrl.io.imm === IMM_MODE.IMM_R,
    rs2,
    Utility.extendSign(imm(11, 0)))
  alu.io.alu_op := mode

  brc.io.cond.rs1     := rs1
  brc.io.cond.rs2     := rs2
  brc.io.cond.br_type := mode(2, 0)
  brc.io.jump.disp    := imm(12, 0)
  brc.io.jump.PC      := io.pc

  jmp.io.mode := mode(0)
  jmp.io.pc   := io.pc
  jmp.io.base := rs1
  jmp.io.imm  := imm

  lds.io.inst := io.inst
  lds.io.mode := mode
  lds.io.en   := io.en & (ctrl.io.sel === ACT.LDS)
  lds.io.rs3  := imm
  lds.io.rs2  := rs2
  lds.io.rs1  := rs1
  lds.io.mem  <> io.mem

  aui.io.pc  := io.pc
  aui.io.imm := imm

  lui.io.imm := imm

  fen.io.inst := io.inst

  sys.io.inst := io.inst

  io.data := MuxLookup(ctrl.io.sel, 0.U(32.W), Seq(
    ACT.ALU -> alu.io.out,
    ACT.LDS -> lds.io.ret,
    ACT.LUI -> lui.io.ret,
    ACT.AUI -> aui.io.ret,
    ACT.JMP -> jmp.io.ret))

  io.rd := MuxLookup(ctrl.io.sel, io.inst(11, 7), Seq(
    ACT.BRC -> 0.U(5.W),
    ACT.LDS -> lds.io.rd))

  io.npc := MuxLookup(ctrl.io.sel, 0.U(32.W), Seq(
    ACT.BRC -> brc.io.jmpAddr,
    ACT.JMP -> jmp.io.jaddr,
    ACT.AUI -> aui.io.ret))

  io.jmp := MuxLookup(ctrl.io.sel, false.B, Seq(
    ACT.BRC -> brc.io.isJmp,
    ACT.JMP -> true.B,
    ACT.AUI -> true.B))

  io.sys := ctrl.io.sel === ACT.SYS

  io.ready := MuxLookup(ctrl.io.sel, io.en, Seq(
    ACT.LDS -> lds.io.ready))

}


package rself

import chisel3._
import chisel3.util._

import IMM_MODE._

class ImmGenIO extends Bundle {
  val inst  = Input(UInt(32.W))
  val mode  = Input(UInt(3.W))
  val value = Output(UInt(32.W))
}

class ImmGen extends Module {


package rself

import chisel3._
import chisel3.util._

class MemoryReaderIO extends Bundle {
<TARGET>
  val ren  = Input(Bool())
  val data = Output(UInt(32.W))
  val vaild = Output(Bool())
}

class MemoryWriterIO extends Bundle {

  val mode = Input(UInt(2.W))
  val wen  = Input(Bool())
  val data = Input(UInt(32.W))
  val vaild = Output(Bool())
}

class MemoryIO extends Bundle {
  val writer = new MemoryWriterIO
  val reader = new MemoryReaderIO
}


package rself

import chisel3._
import chisel3.util._

class Jump extends Module {
  val io = IO(new Bundle {
    val mode = Input(Bool())
    val pc   = Input(UInt(32.W))
    val imm  = Input(UInt(32.W))
    val base = Input(UInt(32.W))
    val ret   = Output(UInt(32.W))
    val jaddr = Output(UInt(32.W))
  })

  io.ret   := io.pc + 4.U(32.W)
  io.jaddr := Mux(io.mode, s(20), s(11)) + Mux(io.mode, io.pc, io.base)
}

class AddUpperImmPC extends Module {
  val io = IO(new Bundle {
    val pc   = Input(UInt(32.W))
    val imm  = Input(UInt(32.W))
    val ret = Output(UInt(32.W))
  })
  io.ret := io.imm + io.pc
}

class LoadUpperImm extends Module {
  val io = IO(new Bundle {
    val imm  = Input(UInt(32.W))
    val ret = Output(UInt(32.W))
  })
  io.ret := io.imm
}

def s(i: Int): UInt = Utility.extendSign(io.imm(i, 0), 32)


package rself

import chisel3._
import chisel3.util._

class LoadIO extends Bundle {
  val mode = Input(UInt(3.W))
  val imm  = Input(UInt(12.W))
  val rs1  = Input(UInt(32.W))
  val en   = Input(Bool())
  val ready = Output(Bool())
  val data = Output(UInt(32.W))
  val mem = Flipped(new MemoryReaderIO)
}

class StoreIO extends Bundle {
  val mode = Input(UInt(3.W))
  val imm  = Input(UInt(12.W))
  val rs2  = Input(UInt(32.W))
  val rs1  = Input(UInt(32.W))
  val en   = Input(Bool())
  val ready = Output(Bool())
  val mem = Flipped(new MemoryWriterIO)
}

class Load extends Module {
  val io = IO(new LoadIO)
  val enable = Module(new Posedge)
  enable.io.in := io.en
  enable.io.re := ~io.mem.vaild
  io.mem.addr := io.rs1 + Utility.extendSign(io.imm)
  io.mem.ren := enable.io.out
  io.ready := io.mem.vaild

  io.data := MuxLookup(io.mode(1, 0), 0.U(32.W), Seq(
    0.U(2.W) -> UNS(8),
    1.U(2.W) -> UNS(16),
    2.U(2.W) -> UNS(32),
    3.U(2.W) -> UNS(32))) // 32bits core treat 64bits insts as 32bits
}

class Store extends Module {
  val io = IO(new StoreIO)
  val enable = Module(new Posedge)
  enable.io.in := io.en
  enable.io.re := ~io.mem.vaild
  io.mem.addr := io.rs1 + Utility.extendSign(io.imm)
  io.mem.wen := enable.io.out
  io.mem.data := io.rs2
  io.mem.mode := io.mode
  io.ready := io.mem.vaild
}

class LoadStore extends Module {
  val io = IO(new Bundle {
    val inst = Input(UInt(32.W))
    val mode = Input(UInt(4.W))
    val en   = Input(Bool())
    val rs3 = Input(UInt(32.W))
    val rs2 = Input(UInt(32.W))
    val rs1 = Input(UInt(32.W))
    val ret = Output(UInt(32.W))
    val rd = Output(UInt(5.W))
    val ready = Output(Bool())

    val mem = Flipped(new MemoryIO)
  })
  val load  = Module(new Load)
  val store = Module(new Store)
  io.rd := Mux(io.mode(3), 0.U(5.W), io.inst(11, 7))

  load.io.imm := io.rs3(11, 0)
  load.io.rs1 := io.rs1

  store.io.imm := io.rs3(11, 0)
  store.io.rs2 := io.rs2
  store.io.rs1 := io.rs1

  load.io.en  := (~io.mode(3)) & io.en
  store.io.en := ( io.mode(3)) & io.en

  load.io.mode := io.mode(2, 0)
  store.io.mode := io.mode(2, 0)

  io.mem.reader <> load.io.mem
  io.mem.writer <> store.io.mem

  io.ret := load.io.data
  io.ready := Mux(io.mode(3), store.io.ready, load.io.ready)
}

def UNS(i: Int):UInt = Mux(~io.mode(2), Utility.extendSign(io.mem.data(i-1, 0), 32), io.mem.data(i-1, 0))


package rself

import chisel3._

class RegFileReaderIO extends Bundle {
  val raddr1 = Input(UInt(5.W))
  val raddr2 = Input(UInt(5.W))
  val rdata1 = Output(UInt(32.W))
  val rdata2 = Output(UInt(32.W))
}

class RegFileWriterIO extends Bundle {
  val wen    = Input(Bool())
  val waddr  = Input(UInt(5.W))
  val wdata  = Input(UInt(32.W))
}

class RegFileIO extends Bundle {
  val reader = new RegFileReaderIO
  val writer = new RegFileWriterIO
}

class RegFile extends Module {
  val io = IO(new RegFileIO)
  val regs = Mem(32, UInt(32.W))
  io.reader.rdata1 := Mux(io.reader.raddr1.orR, regs(io.reader.raddr1), 0.U)
  io.reader.rdata2 := Mux(io.reader.raddr2.orR, regs(io.reader.raddr2), 0.U)

  when(io.writer.wen & io.writer.waddr.orR) {
    regs(io.writer.waddr) := io.writer.wdata
  }


package rself

import chisel3._
import chisel3.util._

class MemoryOperation extends Module {
  val io = IO(new Bundle {

class SystemOperation extends Module {
  val io = IO(new Bundle {
<TARGET>

    val inst = Input(UInt(32.W))
  })
  // Execute no Operation
}


package rself

import chisel3._
import chisel3.util._

object Utility {
  def extendSign(m: UInt, b: Int = 32): UInt = {
    val extendWire = Wire(SInt(b.W))
    extendWire := m.asSInt
    extendWire.asUInt
  }
}

class Posedge extends Module {
  val io = IO(new Bundle {

    val in  = Input(Bool())
    val re  = Input(Bool())
    val out = Output(Bool())
  })


package rself

import chisel3._
import chisel3.util._
import chisel3.testers._
import org.scalatest.FlatSpec

class ALUTester(alu: => ALU) extends BasicTester with TestUtils {
  val tester = Module(alu)
  val alu_A   = TestList()
  val alu_B   = TestList()
  val alu_op  = TestList()
  val alu_out = TestList()
  for (op <- Constants.ALU; i <- 0 until 10) {
    val (_, opcode) = op
    val (a, b) = (random32(), random32())
    alu_op += opcode; alu_A += a; alu_B += b
    alu_out += calculate(a, b, opcode) // TestUtils::calculate
  }
  alu_A += 0L; alu_B += 0xfece1234L; alu_op += 15; alu_out += 0xfece1234L // test invaild operator
  shuffle(alu_A, alu_B, alu_op, alu_out)
  alu_A += 0L; alu_B += 0xfece1234L; alu_op += 15; alu_out += 0xfece1234L // can not reach last data

  val a = interface(alu_A)
  val b = interface(alu_B)
  val p = interface(alu_op, 4)
  val o = interface(alu_out)
  val (cntr, done) = Counter(true.B, o.size)
  tester.io.A := a(cntr)
  tester.io.B := b(cntr)
  tester.io.alu_op := p(cntr)

  printf("Counter: %d, Op: %d, A: 0x%x, B: 0x%x, Out: %x ?= %x\n",
    cntr, tester.io.alu_op, tester.io.A, tester.io.B, tester.io.out, o(cntr))
  assert(tester.io.out === o(cntr))
}

class ALUTests extends FlatSpec {
  "ALU" should "pass" in {
    assert(TesterDriver execute (() => new ALUTester(new ALU)))
  }
}


package rself

import chisel3._
import chisel3.util._
import chisel3.testers._
import org.scalatest.FlatSpec

class BranchTester(brc: => Branch) extends BasicTester with TestUtils {
  val tester = Module(brc)
  val rs1  = TestList()
  val rs2  = TestList()
  val jmty = TestList()
  val disp = TestList()
  val pc   = TestList()
  val isJm = TestList()
  val addr = TestList()

  for (func <- Constants.BranchCodes; i <- 0 until 10) {
    val (fn, fid) = func
    jmty += fid
    val rrs = (fn match {
      case "BEQ" | "BNE" => {
        val a = random32()
        if (rnd.nextBoolean()) {
          (a, a)
        } else {
          (a, random32())
        }
      }
      case _ => (random32(), random32())
    })
    rs1 += rrs._1; rs2 += rrs._2

    val rdisp = randombits(13)
    disp += rdisp

    val cpc = random32()
    val jpc = (cpc + rdisp.toSigned(13)).toUnsigned()
    pc += cpc; addr += jpc

    isJm += branchCond(fn, rrs) // TestUtils::branchCond
  }
  shuffle(rs1, rs2, jmty, disp, pc, isJm, addr)
  rs1 += 0; rs2 += 0; jmty += BR_TYPE.BR_EQ.litValue().toLong; disp += 0; pc += 0; isJm += 1; addr += 0 // unreachable test

  val brc_cond_rs1 = interface(rs1)
  val brc_cond_rs2 = interface(rs2)
  val brc_cond_type = interface(jmty, 3)
  val brc_jump_disp = interface(disp, 13)
  val brc_jump_pc   = interface(pc)
  val brc_out_isJmp = interface(isJm, 1)
  val brc_out_jAddr = interface(addr)

  val (cntr, done) = Counter(true.B, brc_jump_pc.size)

  tester.io.cond.rs1 := brc_cond_rs1(cntr)
  tester.io.cond.rs2 := brc_cond_rs2(cntr)
  tester.io.cond.br_type := brc_cond_type(cntr)
  tester.io.jump.disp := brc_jump_disp(cntr)
  tester.io.jump.PC := brc_jump_pc(cntr)

  printf("Counter: %d, Br: %d, A: %x, B: %x, isJump: %b ?= %b, PC: %x, disp: %x, jumpAddr: %x ?= %x\n",
    cntr, tester.io.cond.br_type, tester.io.cond.rs1, tester.io.cond.rs2,
    tester.io.isJmp, brc_out_isJmp(cntr),
    tester.io.jump.PC, tester.io.jump.disp,
    tester.io.jmpAddr, brc_out_jAddr(cntr))
  assert(tester.io.isJmp === brc_out_isJmp(cntr))
  assert(tester.io.jmpAddr === brc_out_jAddr(cntr))
}

class BranchTests extends FlatSpec {
  "Branch" should "pass" in {
    assert(TesterDriver execute (() => new BranchTester(new Branch)))
  }
}


// auto generate by python scripts

package rself

import chisel3._

object Constants {
  val ALU = Map(
    "ADD"  -> 0L,
    "SUB"  -> 8L,
    "SLL"  -> 1L,


package rself

import chisel3._
import chisel3.util._
import chisel3.testers._
import org.scalatest.FlatSpec

class ControlTester(ctl: => Control) extends BasicTester with TestUtils {
  val tester = Module(ctl)
  val (ctl_inst, ctl_mode, ctl_sel, ctl_imm) = randomInsts() // TestUtils::randomInsts
  ctl_inst += 127; ctl_mode += 0; ctl_sel += 4; ctl_imm += 6 // invaild instruction

  shuffle(ctl_inst, ctl_mode, ctl_sel, ctl_imm)
  ctl_inst += 127; ctl_mode += 0; ctl_sel += 4; ctl_imm += 6 // unreachable test

  val inst = interface(ctl_inst)
  val mod  = interface(ctl_mode, 4)
  val sel  = interface(ctl_sel, 3)
  val imm  = interface(ctl_imm, 3)
  val (cntr, done) = Counter(true.B, inst.size)
  tester.io.inst := inst(cntr)

  printf("Counter: %d, Inst: %b, mod: %x ?= %x, sel: %x ?= %x, imm: %x ?= %x\n",
    cntr, tester.io.inst, tester.io.mode, mod(cntr), tester.io.sel, sel(cntr), tester.io.imm, imm(cntr))
  assert(tester.io.mode === mod(cntr))
  assert(tester.io.sel  === sel(cntr))
  assert(tester.io.imm  === imm(cntr))
}

class ControlTests extends FlatSpec {
  "Control" should "pass" in {
    assert(TesterDriver execute (() => new ControlTester(new Control)))
  }
}