package xiangshan.memend

import org.scalatest._
import chiseltest._
import chisel3._
import chisel3.experimental.BundleLiterals._
import chisel3.util._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.must.Matchers
import xiangshan._
import xiangshan.cache.{DCacheLineIO, DCacheWordReq}
import xiangshan.mem.{LoadForwardQueryIO, NewSbuffer}
import xiangshan.testutils._

import scala.util.Random

class SbufferWapper extends XSModule {
  val io = IO(new Bundle() {
    val in = Vec(StorePipelineWidth, Flipped(Decoupled(new DCacheWordReq)))
    val dcache = new DCacheLineIO
    val forward = Vec(LoadPipelineWidth, Flipped(new LoadForwardQueryIO))
    val flush = new Bundle {
      val valid = Input(Bool())
      val empty = Output(Bool())
    } // sbuffer flush
  })
  val sbuffer = Module(new NewSbuffer)
  io <> sbuffer.io

  // fake dcache
  sbuffer.io.dcache.req.ready := true.B
  sbuffer.io.dcache.resp.valid := RegNext(RegNext(RegNext(RegNext(sbuffer.io.dcache.req.valid))))
  sbuffer.io.dcache.resp.bits.meta.id := RegNext(RegNext(RegNext(RegNext(sbuffer.io.dcache.req.bits.meta.id))))
}

class SbufferTest extends AnyFlatSpec
  with ChiselScalatestTester
  with Matchers
  with HasXSParameter
  with ParallelTestExecution
  with HasPartialDecoupledDriver {


  top.Parameters.set(top.Parameters.debugParameters)


  it should "random req" in {
    test(new SbufferWapper{AddSinks()}){ c =>

      def store_enq(addr: Seq[UInt], data: Seq[UInt], mask: Seq[UInt]) ={
        (0 until StorePipelineWidth).map { i =>
          c.io.in(i).valid.poke(true.B)
          c.io.in(i).bits.pokePartial(chiselTypeOf(c.io.in(i).bits).Lit(
            _.mask -> mask(i),
            _.addr -> addr(i),
            _.data -> data(i)
          ))
        }
        c.clock.step(1)
        for (in <- c.io.in){ in.valid.poke(false.B)}
      }

      def forward_req_and_resp(addr: Seq[UInt], data: Seq[UInt], mask:Seq[UInt]) = {
        (0 until LoadPipelineWidth).map{ i =>
          c.io.forward(i).paddr.poke(addr(i))
          c.io.forward(i).mask.poke(mask(i))
          if(c.io.in(i).ready.peek() == true.B) {
            (0 until 8).map { j =>
              c.io.forward(i).forwardData(j).expect(data(i)(j * 8 + 7, j * 8))
            }
          }
        }
      }

      val TEST_SIZE = 100
      for(i <- 0 until TEST_SIZE) {
        val addr = Seq.fill(StorePipelineWidth)((Random.nextLong() & 0x7ffffffff8L).U)// align to block size
        val data = Seq.fill(StorePipelineWidth)((Random.nextLong() & 0x7fffffffffffffffL).U)
        val mask = Seq.fill(StorePipelineWidth)(0xff.U)
        store_enq(addr, data, mask)
        forward_req_and_resp(addr, data, mask)
      }
    }
  }

  it should "sequence req" in {
    test(new SbufferWapper{AddSinks()}){ c =>

      def store_enq(addr: Seq[UInt], data: Seq[UInt], mask: Seq[UInt]) = {
        (0 until StorePipelineWidth).map { i =>
          c.io.in(i).valid.poke(true.B)
          c.io.in(i).bits.pokePartial(chiselTypeOf(c.io.in(i).bits).Lit(
            _.mask -> mask(i),
            _.addr -> addr(i),
            _.data -> data(i)
          ))
        }
        c.clock.step(1)
        for (in <- c.io.in){ in.valid.poke(false.B)}
      }

      def forward_req_and_resp(addr: Seq[UInt], data: Seq[UInt], mask:Seq[UInt]) = {
        (0 until LoadPipelineWidth).map{ i =>
          c.io.forward(i).paddr.poke(addr(i))
          c.io.forward(i).mask.poke(mask(i))
          if(c.io.in(i).ready.peek() == true.B) {
            (0 until 8).map { j =>
              c.io.forward(i).forwardData(j).expect(data(i)(j * 8 + 7, j * 8))
            }
          }
        }
      }

      val TEST_SIZE = 100
      val start_addr = Random.nextLong() & 0x7ffffffff8L
      for(i <- 0 until TEST_SIZE) {
        val addr = Seq(((i<<4) + start_addr).U,((i<<4)+8+start_addr).U)
        val data = Seq.fill(StorePipelineWidth)((Random.nextLong() & 0x7fffffffffffffffL).U)
        val mask = Seq.fill(StorePipelineWidth)(0xff.U)
        store_enq(addr, data, mask)
        forward_req_and_resp(addr, data, mask)
      }
    }
  }
}
