package cache

import chisel3._
import chisel3.util._
import chiseltest._
import org.scalatest._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.must.Matchers
import xiangshan.testutils._
import utils._

import scala.util.Random

trait TestConst {
    val nWays = 4
    val nSets = 64
    val maxTag = 15
    val tagBits = log2Ceil(maxTag)  // 0-15
}

class ReqSignal extends Bundle with TestConst{
   val tag =  (UInt(tagBits.W))
}

class RespSignal extends Bundle {
    val hit = Bool()
    val miss = Bool()
}


class RepTestTop extends Module
    with TestConst
{
    val io = IO(new Bundle{
        val req = Flipped(ValidIO(new ReqSignal))
        val resp = ValidIO(new RespSignal)
    })

    val fakeCache = RegInit(VecInit( (0 until nWays).map{ w => w.U(tagBits.W) } ))

    val req = io.req
    val replacer = ReplacementPolicy.fromString("random",nWays)

    val hitVec = VecInit(fakeCache.map{w =>  req.valid && (req.bits.tag === w) }).asUInt
    val hit = hitVec.orR
    val victimWayMask = UIntToOH(replacer.way)

    when(hit && req.valid) { replacer.access(OHToUInt(hitVec))}
    .elsewhen(!hit && req.valid){       //refill
        fakeCache(replacer.way) := req.bits.tag
    }

    io.resp.valid := io.req.valid
    io.resp.bits.hit := io.req.valid && hit
    io.resp.bits.miss :=  io.req.valid && !hit

    when(io.req.valid){printf("tag:%d  hit :%d  miss:%d \n",req.bits.tag,hit,io.resp.bits.miss)}
    (0 until nWays).map{ w=>
        printf("(%d)  tag%d\n",w.U,fakeCache(w).asUInt)
    }

}

class ReplaceTest extends AnyFlatSpec
    with TestConst 
    with ChiselScalatestTester 
    with Matchers 
    with ParallelTestExecution
    with HasPartialDecoupledDriver {

    it should "run" in {
        test(new RepTestTop){ c =>

            val testnumber = 1000
            val randomGen = scala.util.Random
            var hitCounter = 0
            var missCounter = 0
            var tag = 0

            for(i <- 0 until testnumber){
                if(i%1 == 0){ tag = randomGen.nextInt(maxTag + 1)  }
                c.io.req.valid.poke(true.B)
                c.io.req.bits.tag.poke(tag.U)
                if(c.io.resp.bits.hit.peek().litToBoolean){ hitCounter = hitCounter + 1}
                if(c.io.resp.bits.miss.peek().litToBoolean){ missCounter = missCounter + 1}
                c.clock.step()
            }

            val missRate:Float = missCounter.toFloat/testnumber
            val hitRate:Float = hitCounter.toFloat/testnumber
            println("------Final Result-------\n")
            println("missRate",missRate)
            println("hitRate",hitRate)

        }
    }


}