// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util._
import chisel3.testers._
import junctions._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class GoldCacheIO(p: CacheConfig, nastiParams: NastiBundleParameters, xlen: Int) extends Bundle {
  val req = Flipped(Decoupled(new CacheReq(xlen, xlen)))
  val resp = Decoupled(new CacheResp(xlen))
  val nasti = new NastiBundle(nastiParams)
}

object GoldCacheState extends ChiselEnum {
  val sIdle, sWrite, sWriteAck, sRead = Value
}

class GoldCache(p: CacheConfig, nasti: NastiBundleParameters, xlen: Int) extends Module {
  // local parameters
  val nSets = p.nSets
  val bBytes = p.blockBytes
  val bBits = bBytes << 3
  val blen = log2Ceil(bBytes)
  val slen = log2Ceil(nSets)
  val tlen = xlen - (slen + blen)
  val nWords = bBits / xlen
  val wBytes = xlen / 8
  val byteOffsetBits = log2Ceil(wBytes)
  val dataBeats = bBits / nasti.dataBits

  val io = IO(new GoldCacheIO(p, nasti, xlen))
  val size = log2Ceil(nasti.dataBits / 8).U
  val len = (dataBeats - 1).U

  val dataMemory = Mem(nSets, UInt(bBits.W))
  val tags = Mem(nSets, UInt(tlen.W))
  val v = Mem(nSets, Bool())
  val d = Mem(nSets, Bool())

  val req = io.req.bits
  val tag = (req.addr >> (blen + slen).U).asUInt
  val idx = req.addr(blen + slen - 1, blen)
  val off = req.addr(blen - 1, 0)

  val readData = dataMemory(idx)
  val writeData = (0 until bBytes).foldLeft(0.U) { (write, i) =>
    write | {
      val condition = ((off / 4.U) === (i / 4).U) && (req.mask >> (i & 0x3).U)(0)
      val trueClause = (((req.data >> (8 * (i & 0x3)).U).asUInt & 0xff.U).asUInt << (8 * BigInt(i)).U).asUInt
      val falseClause = readData & (BigInt(0xff) << (8 * i)).U
      Mux(condition, trueClause, falseClause)
    }
  }(bBits - 1, 0)

  import GoldCacheState._
  val state = RegInit(sIdle)
  val (wCnt, wDone) = Counter(state === sWrite, dataBeats)
  val (rCnt, rDone) = Counter(state === sRead && io.nasti.r.valid, dataBeats)

  io.resp.bits.data := readData >> ((off / 4.U) * xlen.U)
  io.resp.valid := false.B
  io.req.ready := false.B
  io.nasti.ar.bits := NastiAddressBundle(nasti)(0.U, ((req.addr >> blen.U).asUInt << blen.U).asUInt, size, len)
  io.nasti.ar.valid := false.B
  io.nasti.aw.bits := NastiAddressBundle(nasti)(0.U, (Cat(tags(idx), idx) << blen.U).asUInt, size, len)
  io.nasti.aw.valid := false.B
  io.nasti.w.bits := NastiWriteDataBundle(nasti)((readData >> (wCnt * nasti.dataBits.U)).asUInt, None, wDone)
  io.nasti.w.valid := state === sWrite
  io.nasti.b.ready := state === sWriteAck
  io.nasti.r.ready := state === sRead

  switch(state) {
    is(sIdle) {
      when(io.req.valid && io.resp.ready) {
        when(v(idx) && (tags(idx) === tag)) {
          when(req.mask.orR) {
            d(idx) := true.B
            dataMemory(idx) := writeData
            printf(
              "[cache] data[%x] <= writeData %x, readData %x, off: %x, req: %x, mask: %b\n",
              idx,
              writeData,
              readData,
              off,
              io.req.bits.data,
              io.req.bits.mask
            )
          }.otherwise {
            printf("[cache] data[%x] => %x, off: %x, resp: %x\n", idx, readData, off, io.resp.bits.data)
          }
          io.req.ready := true.B
          io.resp.valid := true.B
        }.otherwise {
          when(d(idx)) {
            io.nasti.aw.valid := true.B
            state := sWrite
          }.otherwise {
            dataMemory(idx) := 0.U
            io.nasti.ar.valid := true.B
            state := sRead
          }
        }
      }
    }
    is(sWrite) {
      when(wDone) {
        state := sWriteAck
      }
    }
    is(sWriteAck) {
      when(io.nasti.b.valid) {
        dataMemory(idx) := 0.U
        io.nasti.ar.valid := true.B
        state := sRead
      }
    }
    is(sRead) {
      when(io.nasti.r.valid) {
        dataMemory(idx) := readData | (io.nasti.r.bits.data << (rCnt * nasti.dataBits.U)).asUInt
      }
      when(rDone) {
        assert(io.nasti.r.bits.last)
        tags(idx) := tag
        v(idx) := true.B
        state := sIdle
      }
    }
  }
}

object CacheTesterState extends ChiselEnum {
  val sInit, sStart, sWait, sDone = Value
}

object CacheTesterMemState extends ChiselEnum {
  val sMemIdle, sMemWrite, sMemWrAck, sMemRead = Value
}

class CacheTester(cache: => Cache) extends BasicTester {
  /* Target Design */
  val dut = Module(cache)
  // extract parameters from dut
  val p = dut.p
  val xlen = dut.xlen
  val nasti = dut.nasti
  val nSets = p.nSets
  val bBytes = p.blockBytes
  val bBits = bBytes << 3
  val blen = log2Ceil(bBytes)
  val slen = log2Ceil(nSets)
  val tlen = xlen - (slen + blen)
  val nWords = bBits / xlen
  val wBytes = xlen / 8
  val byteOffsetBits = log2Ceil(wBytes)
  val dataBeats = bBits / nasti.dataBits
  val dut_mem = Wire(new NastiBundle(nasti))
  dut_mem.ar <> Queue(dut.io.nasti.ar, 32)
  dut_mem.aw <> Queue(dut.io.nasti.aw, 32)
  dut_mem.w <> Queue(dut.io.nasti.w, 32)
  dut.io.nasti.b <> Queue(dut_mem.b, 32)
  dut.io.nasti.r <> Queue(dut_mem.r, 32)

  /* Gold Model */
  val gold = Module(new GoldCache(p, nasti, xlen))
  val gold_req = WireInit(gold.io.req)
  val gold_resp = WireInit(gold.io.resp)
  val gold_mem = Wire(new NastiBundle(nasti))
  gold.io.req <> Queue(gold_req, 32)
  gold_resp <> Queue(gold.io.resp, 32)
  gold_mem.ar <> Queue(gold.io.nasti.ar, 32)
  gold_mem.aw <> Queue(gold.io.nasti.aw, 32)
  gold_mem.w <> Queue(gold.io.nasti.w, 32)
  gold.io.nasti.b <> Queue(gold_mem.b, 32)
  gold.io.nasti.r <> Queue(gold_mem.r, 32)

  val size = log2Ceil(nasti.dataBits / 8).U
  val len = (dataBeats - 1).U

  /* Main Memory */
  val mem = Mem(1 << 20, UInt(nasti.dataBits.W))
  import CacheTesterMemState._
  val memState = RegInit(sMemIdle)
  val (wCnt, wDone) = Counter(memState === sMemWrite && dut_mem.w.valid && gold_mem.w.valid, dataBeats)
  val (rCnt, rDone) = Counter(memState === sMemRead && dut_mem.r.ready && gold_mem.r.ready, dataBeats)

  dut_mem.ar.ready := false.B
  dut_mem.aw.ready := false.B
  dut_mem.w.ready := false.B
  dut_mem.b.valid := memState === sMemWrAck
  dut_mem.b.bits := NastiWriteResponseBundle(nasti)(0.U)
  dut_mem.r.valid := memState === sMemRead
  dut_mem.r.bits := NastiReadDataBundle(nasti)(0.U, mem((gold_mem.ar.bits.addr >> size).asUInt + rCnt), rDone)
  gold_mem.ar.ready := dut_mem.ar.ready
  gold_mem.aw.ready := dut_mem.aw.ready
  gold_mem.w.ready := dut_mem.w.ready
  gold_mem.b.valid := dut_mem.b.valid
  gold_mem.b.bits := dut_mem.b.bits
  gold_mem.r.valid := dut_mem.r.valid
  gold_mem.r.bits := dut_mem.r.bits

  switch(memState) {
    is(sMemIdle) {
      when(gold_mem.aw.valid && dut_mem.aw.valid) {
        assert(
          dut_mem.aw.bits.id === gold_mem.aw.bits.id,
          "* dut.io.nasti.aw.bits.id => %x != %x *\n",
          dut_mem.aw.bits.id,
          gold_mem.aw.bits.id
        )
        assert(
          gold_mem.aw.bits.addr === dut_mem.aw.bits.addr,
          "* dut.io.nasti.aw.bits.addr => %x != %x *\n",
          dut_mem.aw.bits.addr,
          gold_mem.aw.bits.addr
        )
        assert(
          gold_mem.aw.bits.size === dut_mem.aw.bits.size,
          "* dut.io.nasti.aw.bits.size => %x != %x *\n",
          dut_mem.aw.bits.size,
          gold_mem.aw.bits.size
        )
        assert(
          gold_mem.aw.bits.len === dut_mem.aw.bits.len,
          "* dut.io.nasti.aw.bits.len => %x != %x *\n",
          dut_mem.aw.bits.len,
          gold_mem.aw.bits.len
        )
        memState := sMemWrite
      }.elsewhen(gold_mem.ar.valid && dut_mem.ar.valid) {
        assert(
          dut_mem.ar.bits.id === gold_mem.ar.bits.id,
          "* dut.io.nasti.ar.bits.id => %x != %x *\n",
          dut_mem.ar.bits.id,
          gold_mem.ar.bits.id
        )
        assert(
          gold_mem.ar.bits.addr === dut_mem.ar.bits.addr,
          "* dut.io.nasti.ar.bits.addr => %x != %x *\n",
          dut_mem.ar.bits.addr,
          gold_mem.ar.bits.addr
        )
        assert(
          gold_mem.ar.bits.size === dut_mem.ar.bits.size,
          "* dut.io.nasti.ar.bits.size => %x != %x *\n",
          dut_mem.ar.bits.size,
          gold_mem.ar.bits.size
        )
        assert(
          gold_mem.ar.bits.len === dut_mem.ar.bits.len,
          "* dut.io.nasti.ar.bits.len => %x != %x *\nn",
          dut_mem.ar.bits.len,
          gold_mem.ar.bits.len
        )
        memState := sMemRead
      }
    }
    is(sMemWrite) {
      assert(dut_mem.aw.bits.size === size)
      assert(dut_mem.aw.bits.len === len)
      when(gold_mem.w.valid && dut_mem.w.valid) {
        assert(
          dut_mem.w.bits.data === gold_mem.w.bits.data,
          "* dut.io.nasti.w.bits.data => %x != %x *\n",
          dut_mem.w.bits.data,
          gold_mem.w.bits.data
        )
        assert(
          dut_mem.w.bits.strb === gold_mem.w.bits.strb,
          "* dut.io.nasti.w.bits.strb => %x != %x *\n",
          dut_mem.w.bits.strb,
          gold_mem.w.bits.strb
        )
        assert(
          dut_mem.w.bits.last === gold_mem.w.bits.last,
          "* dut.io.nasti.w.bits.last => %x != %x *\n",
          dut_mem.w.bits.last,
          gold_mem.w.bits.last
        )
        assert(dut_mem.w.bits.strb === ((1 << (nasti.dataBits / 8)) - 1).U) // TODO: release it?
        mem((dut_mem.aw.bits.addr >> size).asUInt + wCnt) := dut_mem.w.bits.data
        printf("[write] mem[%x] <= %x\n", (dut_mem.aw.bits.addr >> size).asUInt + wCnt, dut_mem.w.bits.data)
        dut_mem.w.ready := true.B
      }
      when(wDone) {
        dut_mem.aw.ready := true.B
        memState := sMemWrAck
      }
    }
    is(sMemWrAck) {
      when(gold_mem.b.ready && dut_mem.b.ready) {
        memState := sMemIdle
      }
    }
    is(sMemRead) {
      when(dut_mem.r.ready && gold_mem.r.ready) {
        printf("[read] mem[%x] => %x\n", (dut_mem.ar.bits.addr >> size).asUInt + rCnt, dut_mem.r.bits.data)
      }
      when(rDone) {
        dut_mem.ar.ready := true.B
        memState := sMemIdle
      }
    }
  }

  /* Tests */
  val seed = System.currentTimeMillis()
  val rnd = new scala.util.Random(seed)
  println(s"CacheTester using seed $seed")

  def rand_tag = rnd.nextInt(1 << tlen).U(tlen.W)

  def rand_off = (rnd.nextInt(1 << blen) & -4).U(blen.W)
  def rand_data = (0 until (nasti.dataBits / 8))
    .foldLeft(BigInt(0))((r, i) => r | (BigInt(rnd.nextInt(0xff + 1)) << (8 * i)))
    .U(nasti.dataBits.W)
  def rand_mask = (rnd.nextInt((1 << (xlen / 8)) - 1) + 1).U((xlen / 8).W)
  def test(tag: UInt, idx: UInt, off: UInt, mask: UInt = 0.U((xlen / 8).W)) =
    Cat(mask, Cat(Seq.fill(bBits / nasti.dataBits)(rand_data)), tag, idx, off)

  val tags = Vector.fill(3)(rand_tag)
  val idxs = Vector.fill(2)(rand_idx)
  val offs = Vector.fill(6)(rand_off)

  val initAddr = for {
    tag <- tags
    idx <- idxs
    off <- 0 until dataBeats
  } yield Cat(tag, idx, off.U)
  val initData = Seq.fill(initAddr.size)(rand_data)
  val testVec = Seq(
    test(tags(0), idxs(0), offs(0)), // #0: read miss
    test(tags(0), idxs(0), offs(1)), // #1: read hit
    test(tags(1), idxs(0), offs(0)), // #2: read miss
    test(tags(1), idxs(0), offs(2)), // #3: read hit
    test(tags(1), idxs(0), offs(3)), // #4: read hit
    test(tags(1), idxs(0), offs(4), rand_mask), // #5: write hit
    test(tags(1), idxs(0), offs(4)), // #6: read hit
    test(tags(2), idxs(0), offs(5)), // #7: read miss & write back
    test(tags(0), idxs(1), offs(0), rand_mask), // #8: write miss
    test(tags(0), idxs(1), offs(0)), // #9: read hit
    test(tags(0), idxs(1), offs(1)), // #10: read hit
    test(tags(1), idxs(1), offs(2), rand_mask), // #11: write miss & write back
    test(tags(1), idxs(1), offs(3)), // #12: read hit
    test(tags(2), idxs(1), offs(4)), // #13: read write back
    test(tags(2), idxs(1), offs(5)) // #14: read hit
  )

  import CacheTesterState._
  val state = RegInit(sInit)
  val timeout = Reg(UInt(32.W))
  val (initCnt, initDone) = Counter(state === sInit, initAddr.size)
  val (testCnt, testDone) = Counter(state === sDone, testVec.size)
  val mask = (VecInit(testVec)(testCnt) >> (blen + slen + tlen + bBits)).asUInt
  val data = (VecInit(testVec)(testCnt) >> (blen + slen + tlen))(bBits - 1, 0)
  val tag = (VecInit(testVec)(testCnt) >> (blen + slen).U)(tlen - 1, 0)
  val idx = (VecInit(testVec)(testCnt) >> blen.U)(slen - 1, 0)
  val off = VecInit(testVec)(testCnt)(blen - 1, 0)
  dut.io.cpu.req.bits.addr := Cat(tag, idx, off)
  dut.io.cpu.req.bits.data := data
  dut.io.cpu.req.bits.mask := mask
  dut.io.cpu.req.valid := state === sWait
  dut.io.cpu.abort := DontCare
  gold_req.bits := dut.io.cpu.req.bits
  gold_req.valid := state === sStart
  gold_resp.ready := state === sDone

  switch(state) {
    is(sInit) {
      mem(VecInit(initAddr)(initCnt)) := VecInit(initData)(initCnt)
      printf("[init] mem[%x] <= %x\n", VecInit(initAddr)(initCnt), VecInit(initData)(initCnt))
      when(initDone) {
        state := sStart
      }
    }
    is(sStart) {
      when(gold_req.ready) {
        timeout := 0.U
        state := sWait
      }
    }
    is(sWait) {
      timeout := timeout + 1.U
      assert(timeout < 100.U)
      when(dut.io.cpu.resp.valid && gold_resp.valid) {
        when(!mask.orR) {
          assert(
            dut.io.cpu.resp.bits.data === gold_resp.bits.data,
            "* dut.io.cpu.resp.bits.data => %x ?= %x *\n",
            dut.io.cpu.resp.bits.data,
            gold_resp.bits.data
          )
        }
        state := sDone
      }
    }
    is(sDone) {
      state := sStart
    }
  }

  when(testDone) { stop() }
}

class CacheTests extends AnyFlatSpec with ChiselScalatestTester {
  val p = MiniConfig()

  "Cache" should "pass with verilator" in {
    test(new CacheTester(new Cache(p.cache, p.nasti, p.core.xlen)))
      .withAnnotations(Seq(VerilatorBackendAnnotation))
      .runUntilStop()
  }

  "Cache" should "pass with treadle" in {
    test(new CacheTester(new Cache(p.cache, p.nasti, p.core.xlen))).runUntilStop()
  }
}

def rand_idx = rnd.nextInt(1 << slen).U(slen.W)


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.testers._
import chisel3.util.experimental.loadMemoryFromFileInline
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class CoreTester(core: => Core, benchmark: String, trace: Boolean = false) extends BasicTester {
  val originalHexFile = os.rel / "tests" / f"$benchmark.hex"
  val resizedHexFile = os.rel / "tests" / "32" / f"$benchmark.hex"
  TestUtils.resizeHexFile(os.pwd / originalHexFile, os.pwd / resizedHexFile, 32) // we have 32 bits per memory entry

  val dut = Module(core)
  val xlen = dut.conf.xlen
  dut.io.host.fromhost.bits := DontCare
  dut.io.host.fromhost.valid := false.B

  val imem = Mem(1 << 20, UInt(xlen.W))
  loadMemoryFromFileInline(imem, resizedHexFile.toString())
  val dmem = Mem(1 << 20, UInt(xlen.W))
  loadMemoryFromFileInline(dmem, resizedHexFile.toString())

  val cycle = RegInit(0.U(32.W))
  val iaddr = dut.io.icache.req.bits.addr / (xlen / 8).U
  val daddr = dut.io.dcache.req.bits.addr / (xlen / 8).U
  val write = (0 until (xlen / 8)).foldLeft(0.U(xlen.W)) { (write, i) =>
    write |
      (Mux(
        (dut.io.dcache.req.valid && dut.io.dcache.req.bits.mask(i)).asBool,
        dut.io.dcache.req.bits.data,
        dmem(daddr)
      )(8 * (i + 1) - 1, 8 * i) << (8 * i).U).asUInt
  }
  dut.io.icache.resp.valid := !reset.asBool
  dut.io.dcache.resp.valid := !reset.asBool
  dut.io.icache.resp.bits.data := RegNext(imem(iaddr))
  dut.io.dcache.resp.bits.data := RegNext(dmem(daddr))

  when(dut.io.icache.req.valid) {
    if (trace) printf("INST[%x] => %x\n", iaddr * (xlen / 8).U, imem(iaddr))
  }
  when(dut.io.dcache.req.valid) {
    when(dut.io.dcache.req.bits.mask.orR) {
      dmem(daddr) := write
      if (trace) printf("MEM[%x] <= %x\n", daddr * (xlen / 8).U, write)
    }.otherwise {

    }
  }
  cycle := cycle + 1.U
  when(dut.io.host.tohost =/= 0.U) {
    printf("cycles: %d\n", cycle)
    assert((dut.io.host.tohost >> 1.U).asUInt === 0.U, "* tohost: %d *\n", dut.io.host.tohost)
    stop()
  }
}

object DefaultCoreConfig {
  def apply() = MiniConfig().core
}

class CoreSimpleTests extends AnyFlatSpec with ChiselScalatestTester {
  behavior.of("Core")
  it should "execute a simple test" in {
    test(new CoreTester(new Core(DefaultCoreConfig()), "rv32ui-p-simple")).runUntilStop(100)
  }
}

abstract class CoreTests(cfg: TestConfig, useVerilator: Boolean = false)
    extends AnyFlatSpec
    with ChiselScalatestTester {
  behavior.of("Core")
  val opts = if (useVerilator) Seq(VerilatorBackendAnnotation) else Seq()
  cfg.tests.foreach { name =>
    it should s"execute $name" taggedAs IntegrationTest in {
      test(new CoreTester(new Core(DefaultCoreConfig()), name)).withAnnotations(opts).runUntilStop(cfg.maxcycles)
    }
  }
}

class CoreISATests extends CoreTests(ISATests)
class CoreBmarkTests extends CoreTests(BmarkTests, true)

if (trace) printf("MEM[%x] => %x\n", daddr * (xlen / 8).U, dmem(daddr))


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.testers._
import chisel3.util._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

object DatapathTesterState extends ChiselEnum {
  val sInit, sRun = Value
}

class DatapathTester(datapath: => Datapath, testType: DatapathTest) extends BasicTester with TestUtils {
  val dut = Module(datapath)
  val ctrl = Module(new Control)
  val xlen = dut.conf.xlen

  dut.io.ctrl <> ctrl.io
  dut.io.host.fromhost.bits := DontCare
  dut.io.host.fromhost.valid := false.B

  override val insts = tests(testType)

  import DatapathTesterState._
  val state = RegInit(sInit)
  val (cntr, done) = Counter(state === sInit, insts.size)
  val timeout = RegInit(0.U(32.W))
  val mem = Mem(1 << 20, UInt(xlen.W))
  val iaddr = dut.io.icache.req.bits.addr / (xlen / 8).U
  val daddr = dut.io.dcache.req.bits.addr / (xlen / 8).U
  val write = (0 until (xlen / 8)).foldLeft(0.U) { (data, i) =>
    data |
      (Mux(dut.io.dcache.req.bits.mask(i), dut.io.dcache.req.bits.data, mem(daddr)) & (BigInt(0xff) << (8 * i)).U)
  }
  dut.reset := state === sInit
  dut.io.icache.resp.bits.data := RegNext(mem(iaddr))
  dut.io.icache.resp.valid := state === sRun
  dut.io.dcache.resp.bits.data := RegNext(mem(daddr))
  dut.io.dcache.resp.valid := state === sRun

  switch(state) {
    is(sInit) {
      (0 until Consts.PC_START by 4).foreach { addr =>
        mem((addr / 4).U) := (if (addr == Consts.PC_EVEC + (3 << 6)) fin else nop)
      }
      mem((Consts.PC_START / (xlen / 8)).U + cntr) := VecInit(insts)(cntr)

    }
    is(sRun) {
      when(dut.io.icache.req.valid) {
        printf(s"INST[%x] => %x, iaddr: %x\n", dut.io.icache.req.bits.addr, mem(iaddr), iaddr)
      }
      when(dut.io.dcache.req.valid) {
        when(dut.io.dcache.req.bits.mask.orR) {
          mem(daddr) := write
          printf("MEM[%x] <= %x\n", dut.io.dcache.req.bits.addr, write)
        }.otherwise {
          printf("MEM[%x] => %x\n", dut.io.dcache.req.bits.addr, mem(daddr))
        }
      }
      timeout := timeout + 1.U
      assert(timeout < 100.U)
      when(dut.io.host.tohost =/= 0.U) {
        assert(
          dut.io.host.tohost === testResults(testType).U,
          s"* tohost: %d != ${testResults(testType)} *",
          dut.io.host.tohost
        )
        stop()
      }
    }
  }
}

class DatapathTests extends AnyFlatSpec with ChiselScalatestTester {
  val p = MiniConfig()
  Seq(BypassTest, ExceptionTest).foreach { tst =>
    "Datapath" should s"pass $tst" in {
      test(new DatapathTester(new Datapath(p.core), tst)).runUntilStop()
    }
  }
}


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.testers._
import chisel3.util._
import chiseltest._
import chiseltest.formal._
import org.scalatest.flatspec.AnyFlatSpec

class ImmGenTester(imm: => ImmGen) extends BasicTester with TestUtils {
  import Control._
  val dut = Module(imm)
  val ctrl = Module(new Control)

  val (cntr, done) = Counter(true.B, insts.size)
  val i = VecInit(insts.map(iimm))
  val s = VecInit(insts.map(simm))
  val b = VecInit(insts.map(bimm))
  val u = VecInit(insts.map(uimm))
  val j = VecInit(insts.map(jimm))
  val z = VecInit(insts.map(zimm))
  val x = VecInit(insts.map(iimm).map(x => (x.litValue & -2).U))
  val out = Mux(
    dut.io.sel === IMM_I,
    i(cntr),
    Mux(
      dut.io.sel === IMM_S,
      s(cntr),
      Mux(
        dut.io.sel === IMM_B,
        b(cntr),
        Mux(
          dut.io.sel === IMM_U,
          u(cntr),
          Mux(dut.io.sel === IMM_J, j(cntr), Mux(dut.io.sel === IMM_Z, z(cntr), x(cntr)))
        )
      )
    )
  )

  ctrl.io.inst := VecInit(insts)(cntr)
  dut.io.inst := ctrl.io.inst
  dut.io.sel := ctrl.io.imm_sel

  assert(dut.io.out === out)
  printf("Counter: %d, Type: 0x%x, Out: %x ?= %x\n", cntr, dut.io.sel, dut.io.out, out)
}

class ImmGenTests extends AnyFlatSpec with ChiselScalatestTester with Formal {
  "ImmGenWire" should "pass" in {
    test(new ImmGenTester(new ImmGenWire(32))).runUntilStop()
  }
  "ImmGenMux" should "pass" in {
    test(new ImmGenTester(new ImmGenMux(32))).runUntilStop()
  }
  "ImmGenMux" should "be equivalent to ImmGenWire" in {
    // since there is no state (registers/memory) in the ImmGen, a single cycle check is enough to prove equivalence
    verify(new ImmGenEquivalenceCheck(new ImmGenMux(32)), Seq(BoundedCheck(1)))
  }
}

class ImmGenEquivalenceCheck(other: => ImmGen) extends Module {
  val dut = Module(other)
  val ref = Module(new ImmGenWire(dut.xlen))

  // arbitrary inputs
  val io = IO(chiselTypeOf(dut.io))

  // connect the same inputs to both modules (the outputs will be overwritten to always connect to the reference)
  dut.io <> io; ref.io <> io

  // check to ensure that outputs are the same
  assert(ref.io.out === dut.io.out, "out: expected: %d, actual: %d", ref.io.out, dut.io.out)
}


// See LICENSE for license details.
package mini

import org.scalatest.Tag

object IntegrationTest extends Tag("IntegrationTest")


// See LICENSE for license details.

package mini

import chisel3._

object Opcode {
  // Special immediate instructions
  val LUI = BigInt("0110111", 2).U(7.W)
  val AUIPC = BigInt("0010111", 2).U(7.W)

  // Jump instructions
  val JAL = BigInt("1101111", 2).U(7.W)
  val JALR = BigInt("1100111", 2).U(7.W)

  // Branch instructions
  val BRANCH = BigInt("1100011", 2).U(7.W)

  // Load and store instrucdtions
  val STORE = BigInt("0100011", 2).U(7.W)
  val LOAD = BigInt("0000011", 2).U(7.W)

  // Arithmetic instructions
  val RTYPE = BigInt("0110011", 2).U(7.W)
  val ITYPE = BigInt("0010011", 2).U(7.W)

  val MEMORY = BigInt("0001111", 2).U(7.W)
  val SYSTEM = BigInt("1110011", 2).U(7.W)
}

object Funct3 {
  // Branch function codes
  val BEQ = BigInt("000", 2).U(3.W)
  val BNE = BigInt("001", 2).U(3.W)
  val BLT = BigInt("100", 2).U(3.W)
  val BGE = BigInt("101", 2).U(3.W)
  val BLTU = BigInt("110", 2).U(3.W)
  val BGEU = BigInt("111", 2).U(3.W)

  // Load and store function codes
  val LB = BigInt("000", 2).U(3.W)
  val LH = BigInt("001", 2).U(3.W)
  val LW = BigInt("010", 2).U(3.W)
  val LBU = BigInt("100", 2).U(3.W)
  val LHU = BigInt("101", 2).U(3.W)
  val SB = BigInt("000", 2).U(3.W)
  val SH = BigInt("001", 2).U(3.W)
  val SW = BigInt("010", 2).U(3.W)

  // Arithmetic R-type and I-type functions codes
  val ADD = BigInt("000", 2).U(3.W)
  val SLL = BigInt("001", 2).U(3.W)
  val SLT = BigInt("010", 2).U(3.W)
  val SLTU = BigInt("011", 2).U(3.W)
  val XOR = BigInt("100", 2).U(3.W)
  val OR = BigInt("110", 2).U(3.W)
  val AND = BigInt("111", 2).U(3.W)
  val SR = BigInt("101", 2).U(3.W)

  val CSRRW = BigInt("001", 2).U(3.W)
  val CSRRS = BigInt("010", 2).U(3.W)
  val CSRRC = BigInt("011", 2).U(3.W)

  val CSRRWI = BigInt("101", 2).U(3.W)
  val CSRRSI = BigInt("110", 2).U(3.W)


// See LICENSE for license details.

package mini

trait TestConfig {
  def tests:      List[String]
  def maxcycles:  Int
  def namePrefix: String
}

case object ISATests extends TestConfig {
  val tests = List(
    "simple",
    "add",
    "addi",
    "auipc",
    "and",
    "andi", // TODO: "fence_i",
    "sb",
    "sh",
    "sw",
    "lb",
    "lbu",
    "lh",
    "lhu",
    "lui",
    "lw",
    "beq",
    "bge",
    "bgeu",
    "blt",
    "bltu",
    "bne",
    "j",
    "jal",
    "jalr",
    "or",
    "ori",
    "sll",
    "slli",
    "slt",
    "slti",
    "sra",
    "srai",
    "sub",
    "xor",
    "xori"

    List(
      "sbreak",
      "scall",
      "illegal",
      "ma_fetch",
      "ma_addr",
      "csr" //, TODO: "timer"
    ).map(t => s"rv32mi-p-$t")
  val maxcycles = 15000
  val namePrefix = "ISATests"
}

case object BmarkTests extends TestConfig {
  val tests = List(
    "median.riscv",
    "multiply.riscv",
    "qsort.riscv",
    "towers.riscv",
    "vvadd.riscv"
  )
  val maxcycles = 1500000
  val namePrefix = "BmarkTests"
}

case object LargeBmarkTests extends TestConfig {
  val tests = List(
    "median.riscv-large",
    "multiply.riscv-large",
    "qsort.riscv-large",
    "towers.riscv-large",
    "vvadd.riscv-large"
  )
  val maxcycles = 5000000
  val namePrefix = "LargeBmarkTests"
}


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.util._
import Instructions._
import junctions.NastiBundleParameters
import org.scalatest.flatspec.AnyFlatSpec

import scala.language.implicitConversions

trait DatapathTest
object BypassTest extends DatapathTest {
  override def toString: String = "bypass test"
}
object ExceptionTest extends DatapathTest {
  override def toString: String = "exception test"
}
// Define your own test

trait TestUtils {
  implicit def boolToBoolean(x:   Bool):   Boolean = x.litValue == 1
  implicit def bitPatToUInt(b:    BitPat): UInt = BitPat.bitPatToUInt(b)
  implicit def uintToBitPat(u:    UInt):   BitPat = BitPat(u)
  implicit def bigIntToInt(x:     BigInt): Int = x.toInt
  implicit def bigIntToBoolean(x: BigInt): Boolean = x != 0
  def toBigInt(x:                 Int):    BigInt = (BigInt(x >>> 1) << 1) | (x & 0x1)

  def rs1(inst: UInt) = ((inst.litValue >> 15) & 0x1f).toInt
  def rs2(inst: UInt) = ((inst.litValue >> 20) & 0x1f).toInt
  def rd(inst:  UInt) = ((inst.litValue >> 7) & 0x1f).toInt
  def csr(inst: UInt) = inst.litValue >> 20
  def reg(x:    Int) = (x & ((1 << 5) - 1)).U(5.W)
  def imm(x:    Int) = (x & ((1 << 20) - 1)).S(21.W)
  def Cat(l:    Seq[Bits]): UInt = l.tail.foldLeft(l.head.asUInt) { (x, y) =>
    assert(x.isLit && y.isLit)
    (x.litValue << y.getWidth | y.litValue).U((x.getWidth + y.getWidth).W)
  }
  def Cat(x: Bits, l: Bits*): UInt = Cat(x :: l.toList)
  val fence = Cat(0.U(4.W), 0xf.U(4.W), 0xf.U(4.W), 0.U(13.W), Opcode.MEMORY)
  val nop = Cat(0.U(12.W), reg(0), Funct3.ADD, reg(0), Opcode.ITYPE)
  val csrRegs = CSR.regs.map(_.litValue)
  private val csrMap = csrRegs
    .zip(
      List(
        "cycle",
        "time",
        "instret",
        "cycleh",
        "timeh",
        "instreth",
        "cyclew",
        "timew",
        "instretw",
        "cyclehw",
        "timehw",
        "instrethw",
        "mcpuid",
        "mimpid",
        "mhartid",
        "mtvec",
        "mtdeleg",
        "mie",
        "mtimecmp",
        "mtime",
        "mtimeh",
        "mscratch",
        "mepc",
        "mcause",
        "mbadaddr",
        "mip",
        "mtohost",
        "mfromhost",
        "mstatus"
      )
    )
    .toMap
  def csrName(csr: BigInt) = csrMap.getOrElse(csr, csr.toString(16))

  private def inst_31(inst:    UInt) = ((inst.litValue >> 31) & 0x1).U(1.W)
  private def inst_30_25(inst: UInt) = ((inst.litValue >> 25) & 0x3f).U(6.W)
  private def inst_24_21(inst: UInt) = ((inst.litValue >> 21) & 0xf).U(4.W)
  private def inst_20(inst:    UInt) = ((inst.litValue >> 20) & 0x1).U(1.W)
  private def inst_19_12(inst: UInt) = ((inst.litValue >> 12) & 0xff).U(8.W)
  private def inst_11_8(inst:  UInt) = ((inst.litValue >> 8) & 0xf).U(4.W)
  private def inst_7(inst:     UInt) = ((inst.litValue >> 7) & 0x1).U(1.W)

  def simm(inst: UInt) = Cat(Cat(Seq.fill(21) { inst_31(inst) }), inst_30_25(inst), inst_11_8(inst), inst_7(inst))
  def bimm(inst: UInt) =
    Cat(Cat(Seq.fill(20) { inst_31(inst) }), inst_7(inst), inst_30_25(inst), inst_11_8(inst), 0.U(1.W))
  def uimm(inst: UInt) =
    Cat(inst_31(inst), inst_30_25(inst), inst_24_21(inst), inst_20(inst), inst_19_12(inst), 0.U(12.W))
  def jimm(inst: UInt) = Cat(
    Cat(Seq.fill(12) { inst_31(inst) }),
    inst_19_12(inst),
    inst_20(inst),
    inst_30_25(inst),
    inst_24_21(inst),
    0.U(1.W)
  )
  def zimm(inst: UInt) = ((inst.litValue >> 15) & 0x1f).U

  /* Define tests */
  val rnd = new scala.util.Random
  def rand_fn7 = rnd.nextInt(1 << 7).U(7.W)
  def rand_rs2 = (rnd.nextInt((1 << 5) - 1) + 1).U(5.W)
  def rand_rs1 = (rnd.nextInt((1 << 5) - 1) + 1).U(5.W)
  def rand_fn3 = rnd.nextInt(1 << 3).U(3.W)
  def rand_rd = (rnd.nextInt((1 << 5) - 1) + 1).U(5.W)
  def rand_csr = csrRegs(rnd.nextInt(csrRegs.size - 1)).U
  def rand_inst = toBigInt(rnd.nextInt()).U
  def rand_addr = toBigInt(rnd.nextInt()).U
  def rand_data = toBigInt(rnd.nextInt()).U

  val insts: Seq[UInt] = Seq(
    Cat(rand_fn7, rand_rs2, rand_rs1, rand_fn3, rand_rd, Opcode.LUI),
    Cat(rand_fn7, rand_rs2, rand_rs1, rand_fn3, rand_rd, Opcode.AUIPC),
    Cat(rand_fn7, rand_rs2, rand_rs1, rand_fn3, rand_rd, Opcode.JAL),
    Cat(rand_fn7, rand_rs2, rand_rs1, 0.U(3.W), rand_rd, Opcode.JALR),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.BEQ, rand_rd, Opcode.BRANCH),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.BNE, rand_rd, Opcode.BRANCH),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.BLT, rand_rd, Opcode.BRANCH),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.BGE, rand_rd, Opcode.BRANCH),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.BLTU, rand_rd, Opcode.BRANCH),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.BGEU, rand_rd, Opcode.BRANCH),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.LB, rand_rd, Opcode.LOAD),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.LH, rand_rd, Opcode.LOAD),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.LW, rand_rd, Opcode.LOAD),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.LBU, rand_rd, Opcode.LOAD),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.LHU, rand_rd, Opcode.LOAD),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.SB, rand_rd, Opcode.STORE),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.SH, rand_rd, Opcode.STORE),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.SW, rand_rd, Opcode.STORE),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.ADD, rand_rd, Opcode.ITYPE),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.SLT, rand_rd, Opcode.ITYPE),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.SLTU, rand_rd, Opcode.ITYPE),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.XOR, rand_rd, Opcode.ITYPE),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.OR, rand_rd, Opcode.ITYPE),
    Cat(rand_fn7, rand_rs2, rand_rs1, Funct3.AND, rand_rd, Opcode.ITYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.SLL, rand_rd, Opcode.ITYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.SR, rand_rd, Opcode.ITYPE),
    Cat(Funct7.S, rand_rs2, rand_rs1, Funct3.SR, rand_rd, Opcode.ITYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.ADD, rand_rd, Opcode.RTYPE),
    Cat(Funct7.S, rand_rs2, rand_rs1, Funct3.ADD, rand_rd, Opcode.RTYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.SLL, rand_rd, Opcode.RTYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.SLT, rand_rd, Opcode.RTYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.SLTU, rand_rd, Opcode.RTYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.XOR, rand_rd, Opcode.RTYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.SR, rand_rd, Opcode.RTYPE),
    Cat(Funct7.S, rand_rs2, rand_rs1, Funct3.SR, rand_rd, Opcode.RTYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.OR, rand_rd, Opcode.RTYPE),
    Cat(Funct7.U, rand_rs2, rand_rs1, Funct3.AND, rand_rd, Opcode.RTYPE),
    fence,
    FENCEI,
    Cat(rand_csr, rand_rs1, Funct3.CSRRW, rand_rd, Opcode.SYSTEM),
    Cat(rand_csr, rand_rs1, Funct3.CSRRS, rand_rd, Opcode.SYSTEM),
    Cat(rand_csr, rand_rs1, Funct3.CSRRC, rand_rd, Opcode.SYSTEM),
    Cat(rand_csr, rand_rs1, Funct3.CSRRWI, rand_rd, Opcode.SYSTEM),
    Cat(rand_csr, rand_rs1, Funct3.CSRRSI, rand_rd, Opcode.SYSTEM),
    Cat(rand_csr, rand_rs1, Funct3.CSRRCI, rand_rd, Opcode.SYSTEM),
    ECALL,
    EBREAK,
    ERET,
    nop,
    rand_inst
  )

  def RU(funct3: UInt, rd: Int, rs1: Int, rs2: Int) =
    Cat(Funct7.U, reg(rs2), reg(rs1), funct3, reg(rd), Opcode.RTYPE)
  def RS(funct3: UInt, rd: Int, rs1: Int, rs2: Int) =
    Cat(Funct7.S, reg(rs2), reg(rs1), funct3, reg(rd), Opcode.RTYPE)
  def I(funct3: UInt, rd: Int, rs1: Int, i: Int) =
    Cat(imm(i)(11, 0), reg(rs1), funct3, reg(rd), Opcode.ITYPE)
  def L(funct3: UInt, rd: Int, rs1: Int, i: Int) =
    Cat(imm(i)(11, 0), reg(rs1), funct3, reg(rd), Opcode.LOAD)
  def S(funct3: UInt, rs2: Int, rs1: Int, i: Int) =
    Cat(imm(i)(11, 5), reg(rs2), reg(rs1), funct3, imm(i)(4, 0), Opcode.STORE)
  def B(funct3: UInt, rs1: Int, rs2: Int, i: Int) =
    Cat(imm(i)(12), imm(i)(10, 5), reg(rs2), reg(rs1), funct3, imm(i)(4, 1), imm(i)(11), Opcode.BRANCH)
  def U(op: UInt, rd: Int, i: Int) =
    Cat(imm(i), reg(rd), op)
  def J(rd: Int, i: Int) =
    Cat(imm(i)(20), imm(i)(10, 1), imm(i)(11), imm(i)(19, 12), reg(rd), Opcode.JAL)
  def JR(rd: Int, rs1: Int, i: Int) =
    Cat(imm(i)(11, 0), reg(rs1), 0.U(3.W), reg(rd), Opcode.JALR)
  def SYS(funct3: UInt, rd: Int, csr: UInt, rs1: Int) =
    Cat(csr, reg(rs1), funct3, reg(rd), Opcode.SYSTEM)

  val fin = Cat(CSR.mtohost, reg(31), Funct3.CSRRW, reg(0), Opcode.SYSTEM)
  val bypassTest = List(
    I(Funct3.ADD, 1, 0, 1), // ADDI x1, x0, 1   # x1 <- 1
    S(Funct3.SW, 1, 0, 12), // SW   x1, x0, 12  # Mem[12] <- 1
    L(Funct3.LW, 2, 0, 12), // LW   x2, x0, 12  # x2 <- 1
    RU(Funct3.ADD, 3, 2, 2), // ADD  x3, x2, x2  # x3 <- 2
    RS(Funct3.ADD, 4, 3, 2), // SUB  x4, x2, x3  # x4 <- 1
    RU(Funct3.SLL, 5, 3, 4), // SLL  x5, x2, x4  # x5 <- 4
    RU(Funct3.SLT, 6, 4, 5), // SLT  x6, x4, x5  # x6 <- 1
    B(Funct3.BEQ, 1, 6, 8), // BEQ  x1, x6, 8   # go to the BGE branch
    J(0, 12), // JAL  x0, 12      # skip nop
    B(Funct3.BGE, 4, 1, -4), // BGE  x4, x1, -4  # go to the jump
    nop,
    nop,
    RU(Funct3.ADD, 26, 0, 1), // ADD x26,  x0, x1  # x26 <- 1
    RU(Funct3.ADD, 27, 26, 2), // ADD x27, x26, x2  # x27 <- 2
    RU(Funct3.ADD, 28, 27, 3), // ADD x28, x27, x3  # x28 <- 4
    RU(Funct3.ADD, 29, 28, 4), // ADD x29, x28, x4  # x29 <- 5
    RU(Funct3.ADD, 30, 29, 5), // ADD x30, x29, x5  # x30 <- 9
    RU(Funct3.ADD, 31, 30, 6), // ADD x31, x31, x6  # x31 <- 10
    fin
  )
  val exceptionTest = List(
    fence,
    I(Funct3.ADD, 31, 0, 2), // ADDI x31, x0,  1 # x31 <- 2
    I(Funct3.ADD, 31, 31, 1), // ADDI x31, x31, 1 # x31 <- 3
    I(Funct3.ADD, 31, 31, 1), // ADDI x31, x32, 1 # x31 <- 4
    0.U, // exception
    I(Funct3.ADD, 31, 31, 1), // ADDI x31, x31, 1 # x31 <- 5
    I(Funct3.ADD, 31, 31, 1), // ADDI x31, x31, 1 # x31 <- 6
    I(Funct3.ADD, 31, 31, 1), // ADDI x31, x31, 1 # x31 <- 7
    fin
  )
  val tests = Map(BypassTest -> bypassTest, ExceptionTest -> exceptionTest)
  val testResults = Map(
    BypassTest -> 10,
    ExceptionTest -> 4
  )
}

object TestUtils {
  def resizeHexFile(originalHexFile: os.Path, resizedHexFile: os.Path, resizedWidth: Int): Unit = {
    val hexFileLines = os.read.lines(originalHexFile)
    val resizedLines = resizeHexFileInner(hexFileLines, resizedWidth)
    os.write.over(resizedHexFile, resizedLines.mkString("\n"), createFolders = true)
  }

  def resizeHexFileInner(hexFileLines: Seq[String], resizedWidth: Int): Seq[String] = {
    val hexFileLineLengths = hexFileLines.filter(_.nonEmpty).map(_.length)
    assert(hexFileLineLengths.forall(_ == hexFileLineLengths.head), "hex file has lines of differing lengths")
    val originalWidth = hexFileLineLengths.head * 4 // 4 bits / nibble

    if (originalWidth > resizedWidth) {
      assert(
        originalWidth % resizedWidth == 0,
        f"The original width of the hex file ($originalWidth) is not evenly divisible by the desired resized width ($resizedWidth)"
      )

      hexFileLines.flatMap(_.grouped(resizedWidth / 4).toSeq.reverse)
    } else if (originalWidth < resizedWidth) {
      ???
    } else { // no resizing
      hexFileLines
    }
  }
}

class TestUtilsTests extends AnyFlatSpec {
  "resizeHexFile" should "resize to a smaller word size" in {
    val in = Seq("12345678deadbeef1010101066666666", "11112222333344445555666677778888")
    val out = TestUtils.resizeHexFileInner(in, 32)
    assert(out == Seq("66666666", "10101010", "deadbeef", "12345678", "77778888", "55556666", "33334444", "11112222"))

    val out2 = TestUtils.resizeHexFileInner(in, 64)
    assert(out2 == Seq("1010101066666666", "12345678deadbeef", "5555666677778888", "1111222233334444"))
  }
}

object TestParams {
  val Nasti = NastiBundleParameters(addrBits = 32, dataBits = 64, idBits = 5)
}

def iimm(inst: UInt) = Cat(Cat(Seq.fill(21) { inst_31(inst) }), inst_30_25(inst), inst_24_21(inst), inst_20(inst))


// See LICENSE for license details.

package mini

import chisel3._
import chisel3.testers._
import chisel3.util._
import chisel3.util.experimental.loadMemoryFromFileInline
import chiseltest._
import junctions._
import org.scalatest.flatspec.AnyFlatSpec

object TileTesterState extends ChiselEnum {
  val sIdle, sWrite, sWrAck, sRead = Value
}

class TileTester(tile: => Tile, benchmark: String, latency: Int = 8, trace: Boolean = false) extends BasicTester {
  val originalHexFile = os.rel / "tests" / f"$benchmark.hex"
  val resizedHexFile = os.rel / "tests" / "64" / f"$benchmark.hex"
  TestUtils.resizeHexFile(os.pwd / originalHexFile, os.pwd / resizedHexFile, 64) // we have 64 bits per memory entry

  val dut = Module(tile)
  // extract parameters from design under test
  val nasti = dut.nastiParams

  dut.io.host.fromhost.bits := 0.U
  dut.io.host.fromhost.valid := false.B

  val _mem = Mem(1 << 20, UInt(nasti.dataBits.W))
  loadMemoryFromFileInline(_mem, resizedHexFile.toString())
  import TileTesterState._
  val state = RegInit(sIdle)
  val cycle = RegInit(0.U(32.W))

  val id = Reg(UInt(nasti.idBits.W))
  val addr = Reg(UInt(nasti.addrBits.W))
  val len = Reg(UInt(NastiConstants.LenBits.W))
  val off = Reg(UInt(NastiConstants.LenBits.W))
  val write = (0 until (nasti.dataBits / 8)).foldLeft(0.U(nasti.dataBits.W)) { (write, i) =>
    write |
      (Mux(dut.io.nasti.w.bits.strb(i), dut.io.nasti.w.bits.data, _mem(addr))(
        8 * (i + 1) - 1,
        8 * i
      ) << (8 * i).U).asUInt
  }
  val bpipe = WireInit(dut.io.nasti.b)
  val rpipe = WireInit(dut.io.nasti.r)

  dut.reset := reset.asBool
  dut.io.nasti.aw.ready := state === sIdle
  dut.io.nasti.ar.ready := state === sIdle
  dut.io.nasti.w.ready := state === sWrite
  dut.io.nasti.b <> LatencyPipe(bpipe, latency)
  dut.io.nasti.r <> LatencyPipe(rpipe, latency)
  bpipe.bits := NastiWriteResponseBundle(nasti)(id)
  bpipe.valid := state === sWrAck
  rpipe.bits := NastiReadDataBundle(nasti)(id, _mem(addr + off), off === len)
  rpipe.valid := state === sRead

  val isDone = WireInit(false.B)
  val setDone = WireInit(false.B)

  cycle := cycle + 1.U
  when(dut.io.host.tohost =/= 0.U) {
    isDone := true.B
  }

  setDone := isDone
  when(setDone) {
    printf("cycles: %d\n", cycle)
    assert((dut.io.host.tohost >> 1.U) === 0.U, "* tohost: %d *\n", dut.io.host.tohost)
    stop()
  }

  /**
    *   Master(Tile)    ---------   Slaver(Memory)
    *
    *   Address Write > == AW == >
    *   Data Write    > == W  == >
    *   Response      < == B  == <
    *
    *   Address Read  > == AR == >
    *   Data Read     < == R  == <
    */

  switch(state) {
    is(sIdle) {
      when(dut.io.nasti.aw.valid) {
        assert((1.U << dut.io.nasti.aw.bits.size).asUInt === (nasti.dataBits / 8).U)
        addr := dut.io.nasti.aw.bits.addr / (nasti.dataBits / 8).U
        id := dut.io.nasti.aw.bits.id
        len := dut.io.nasti.aw.bits.len
        off := 0.U
        state := sWrite
      }.elsewhen(dut.io.nasti.ar.valid) {
        assert((1.U << dut.io.nasti.ar.bits.size).asUInt === (nasti.dataBits / 8).U)
        addr := dut.io.nasti.ar.bits.addr / (nasti.dataBits / 8).U
        id := dut.io.nasti.ar.bits.id
        len := dut.io.nasti.ar.bits.len
        off := 0.U
        state := sRead
      }
    }
    is(sWrite) {
      when(dut.io.nasti.w.valid) {
        _mem(addr + off) := write

        when(off === len) {
          assert(dut.io.nasti.w.bits.last)
          state := sWrAck
        }.otherwise {
          off := off + 1.U
        }
      }
    }
    is(sWrAck) {
      when(bpipe.ready) {
        state := sIdle
      }
    }
    is(sRead) {
      when(rpipe.ready) {
        when(off === len) {
          state := sIdle
        }.otherwise {
          off := off + 1.U
        }
      }
    }
  }
}

class LatencyPipeIO[T <: Data](val gen: T) extends Bundle {
  val in = Flipped(Decoupled(gen))
  val out = Decoupled(gen)
}

class LatencyPipe[T <: Data](gen: T, latency: Int) extends Module {
  val io = IO(new LatencyPipeIO(chiselTypeOf(gen)))
  io := DontCare
  io.out <> (0 until latency).foldLeft(io.in)((in, i) => Queue(in, 1, pipe = true))
}

object LatencyPipe {
  def apply[T <: Data](in: DecoupledIO[T], latency: Int) = {
    val pipe = Module(new LatencyPipe(in.bits, latency))
    pipe.io.in <> in
    pipe.io.out
  }
}

class TileSimpleTests extends AnyFlatSpec with ChiselScalatestTester {
  behavior.of("Tile")
  val p = MiniConfig()
  it should "execute a simple test" in {
    test(new TileTester(Tile(p), "rv32ui-p-simple")).runUntilStop(15000)
  }
}

abstract class TileTests(cfg: TestConfig, useVerilator: Boolean = false)
    extends AnyFlatSpec
    with ChiselScalatestTester {
  behavior.of("Tile")
  val opts = if (useVerilator) Seq(VerilatorBackendAnnotation) else Seq()
  val p = MiniConfig()
  cfg.tests.foreach { name =>
    it should s"execute $name" taggedAs IntegrationTest in {
      test(new TileTester(Tile(p), name)).withAnnotations(opts).runUntilStop(cfg.maxcycles)
    }
  }
}

class TileISATests extends TileTests(ISATests, true)
class TileBmarkTests extends TileTests(BmarkTests, true)
class TileLargeBmarkTests extends TileTests(LargeBmarkTests, true)

if (trace) printf("MEM[%x] <= %x\n", (addr + off) * (nasti.dataBits / 8).U, write)


package final_project

import chisel3._
import chisel3.util._

class TrueDualPortWire(w: Int, addr_w: Int) extends Bundle{
    val addra = Input(UInt(addr_w.W))
    val dina = Input(UInt(w.W))
    val douta = Output(UInt(w.W))
    val wea = Input(Bool())
    val addrb = Input(UInt(addr_w.W))
    val dinb = Input(UInt(w.W))
    val doutb = Output(UInt(w.W))
    val web = Input(Bool())
}

class TrueDualPort(w: Int, addr_w: Int) extends Bundle{
    val clka = Input(Clock())
    val clkb = Input(Clock())

    val addra = Input(UInt(addr_w.W))
    val dina = Input(UInt(w.W))
    val douta = Output(UInt(w.W))
    val wea = Input(Bool())
    val addrb = Input(UInt(addr_w.W))
    val dinb = Input(UInt(w.W))
    val doutb = Output(UInt(w.W))
    val web = Input(Bool())
}

object TrueDualPortWire{
    def apply(x: TrueDualPort, w: Int, addr_w: Int): TrueDualPortWire = {
        val ret = Wire(new TrueDualPortWire(w, addr_w))
        x.addra := ret.addra
        x.dina := ret.dina
        ret.douta := x.douta
        x.wea := ret.wea
        x.addrb := ret.addrb
        x.dinb := ret.dinb
        ret.doutb := x.doutb
        x.web := ret.web
        ret.addra := 0.U
        ret.dina := 0.U
        ret.wea := false.B
        ret.addrb := 0.U
        ret.dinb := 0.U
        ret.web := false.B
        return ret
    }
}

class SinglePortROM(w: Int, addr_w: Int) extends Bundle{
    val addra = Input(UInt(addr_w.W))
    val clka = Input(Clock())
    val douta = Output(UInt(w.W))
}

class big_bank0(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new TrueDualPort(w, addr_w))
}

    val io = IO(new TrueDualPort(w, addr_w))
}
class small_bank1(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new TrueDualPort(w, addr_w))
}

class small_bank2(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new TrueDualPort(w, addr_w))
}

class small_bank3(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new TrueDualPort(w, addr_w))
}

class small_bank4(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new TrueDualPort(w, addr_w))
}

class small_bank5(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new TrueDualPort(w, addr_w))
}

class small_bank6(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new TrueDualPort(w, addr_w))
}

class small_bank7(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new TrueDualPort(w, addr_w))
}
class weight0(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new SinglePortROM(w, addr_w))
}

class bias0(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new SinglePortROM(w, addr_w))
}
class weight0_2(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new SinglePortROM(w, addr_w))
}

class bias0_2(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new SinglePortROM(w, addr_w))
}

class RAMGroupBundle(w: Int, addr_w: Int, id_w: Int) extends Bundle{
    val rd_valid_in = Input(Bool())
    val rd_valid_out = Output(Bool())
    val rd_addr1 = Input(new AddressReadGroup(addr_w, id_w))
    val rd_addr2 = Input(new AddressReadGroup(addr_w, id_w))
    val rd_big = Output(Vec(2, new BigBankReadData(w)))
    val rd_small = Output(Vec(2, Vec(4, new SmallBankReadData(w))))
    val wr_addr = Input(new AddressWriteGroup(addr_w, id_w))
    val wr_valid_in = Input(Bool())
    val to_bigbank = Input(new BigBankWriteData(w))
    val to_smallbank = Input(Vec(2, new SmallBankWriteData(w)))
}

class RAMGroup(w: Int, addr_w: Int, id_w: Int) extends Module{
    val io = IO(new RAMGroupBundle(w, addr_w, id_w))

    val reg_valid = RegNext(RegNext(io.rd_valid_in, 0.U), 0.U)
    io.rd_valid_out := reg_valid
    io.rd_big := 0.U.asTypeOf(io.rd_big)
    io.rd_small := 0.U.asTypeOf(io.rd_small)

    val a_big_bank0 = Module(new big_bank0(StdPara.big_bank_w, StdPara.big_bank_addr_w))
    val a_big_bank1 = Module(new big_bank1(StdPara.big_bank_w, StdPara.big_bank_addr_w))

    a_big_bank0.io.clka := clock
    a_big_bank0.io.clkb := clock
    a_big_bank1.io.clka := clock
    a_big_bank1.io.clkb := clock

    val a_small_bank0 = Module(new small_bank0(StdPara.small_bank_w, StdPara.small_bank_addr_w))
    val a_small_bank1 = Module(new small_bank1(StdPara.small_bank_w, StdPara.small_bank_addr_w))
    val a_small_bank2 = Module(new small_bank2(StdPara.small_bank_w, StdPara.small_bank_addr_w))
    val a_small_bank3 = Module(new small_bank3(StdPara.small_bank_w, StdPara.small_bank_addr_w))
    val a_small_bank4 = Module(new small_bank4(StdPara.small_bank_w, StdPara.small_bank_addr_w))
    val a_small_bank5 = Module(new small_bank5(StdPara.small_bank_w, StdPara.small_bank_addr_w))
    val a_small_bank6 = Module(new small_bank6(StdPara.small_bank_w, StdPara.small_bank_addr_w))
    val a_small_bank7 = Module(new small_bank7(StdPara.small_bank_w, StdPara.small_bank_addr_w))

    a_small_bank0.io.clka := clock
    a_small_bank0.io.clkb := clock
    a_small_bank1.io.clka := clock
    a_small_bank1.io.clkb := clock
    a_small_bank2.io.clka := clock
    a_small_bank2.io.clkb := clock
    a_small_bank3.io.clka := clock
    a_small_bank3.io.clkb := clock
    a_small_bank4.io.clka := clock
    a_small_bank4.io.clkb := clock
    a_small_bank5.io.clka := clock
    a_small_bank5.io.clkb := clock
    a_small_bank6.io.clka := clock
    a_small_bank6.io.clkb := clock
    a_small_bank7.io.clka := clock
    a_small_bank7.io.clkb := clock

    val big_banks = Array.fill(2)(new TrueDualPortWire(StdPara.big_bank_w, StdPara.big_bank_addr_w))
    val small_banks = Array.fill(8)(new TrueDualPortWire(StdPara.small_bank_w, StdPara.small_bank_addr_w))

    big_banks(0) = TrueDualPortWire(a_big_bank0.io, StdPara.big_bank_w, StdPara.big_bank_addr_w)
    big_banks(1) = TrueDualPortWire(a_big_bank1.io, StdPara.big_bank_w, StdPara.big_bank_addr_w)

    small_banks(0) = TrueDualPortWire(a_small_bank0.io, StdPara.small_bank_w, StdPara.small_bank_addr_w)
    small_banks(1) = TrueDualPortWire(a_small_bank1.io, StdPara.small_bank_w, StdPara.small_bank_addr_w)
    small_banks(2) = TrueDualPortWire(a_small_bank2.io, StdPara.small_bank_w, StdPara.small_bank_addr_w)
    small_banks(3) = TrueDualPortWire(a_small_bank3.io, StdPara.small_bank_w, StdPara.small_bank_addr_w)
    small_banks(4) = TrueDualPortWire(a_small_bank4.io, StdPara.small_bank_w, StdPara.small_bank_addr_w)
    small_banks(5) = TrueDualPortWire(a_small_bank5.io, StdPara.small_bank_w, StdPara.small_bank_addr_w)
    small_banks(6) = TrueDualPortWire(a_small_bank6.io, StdPara.small_bank_w, StdPara.small_bank_addr_w)
    small_banks(7) = TrueDualPortWire(a_small_bank7.io, StdPara.small_bank_w, StdPara.small_bank_addr_w)

    big_banks(0).addra := io.rd_addr1.addrs(0).addr
    big_banks(1).addra := io.rd_addr2.addrs(0).addr

    io.rd_big(0) := RegNext(big_banks(0).douta.asTypeOf(new BigBankReadData(w)), 0.U.asTypeOf(new BigBankReadData(w)))
    io.rd_big(1) := RegNext(big_banks(1).douta.asTypeOf(new BigBankReadData(w)), 0.U.asTypeOf(new BigBankReadData(w)))

    // magic
    val typ = RegInit(VecInit(Seq.fill(2)(0.U(id_w.W))))
    typ(0) := io.rd_addr1.addrs(2).bank_id
    typ(1) := io.rd_addr2.addrs(2).bank_id

    val magic_mat = Array(Array(0, 2, 3, 1), Array(1, 3, 0, 2), Array(2, 0, 1, 3), Array(3, 1, 2, 0))
    def get_which(bank: Int, addr1: Int): Int = {
        for(i <- 0 to 3) if(addr1==magic_mat(1)(i)){
            for(j <- 0 to 3) if(bank==magic_mat(j)(i))
                return j+1
        }
        return -1
    }

    def get_which_res(rd: Int, addr1: Int): Int = {
        for(i <- 0 to 3) if(addr1==magic_mat(1)(i)){
            return magic_mat(rd)(i)
        }
        return -1
    }

    for(i <- 0 to 3){
        small_banks(i).addra := MuxCase(0.U.asTypeOf(small_banks(i).addra), Seq.tabulate(4){
            j => ((io.rd_addr1.addrs(2).bank_id(j)) -> io.rd_addr1.addrs(get_which(i, j)).addr)
        })
        small_banks(i+4).addra := MuxCase(0.U.asTypeOf(small_banks(i+4).addra), Seq.tabulate(4){
            j => ((io.rd_addr2.addrs(2).bank_id(j+4)) -> io.rd_addr2.addrs(get_which(i, j)).addr)
        })
    }
    for(i <- 0 to 3){
        val x0 = Wire(new SmallBankReadData(w))
        val x1 = Wire(new SmallBankReadData(w))
        x0 := MuxCase(0.U.asTypeOf(new SmallBankReadData(w)), Seq.tabulate(4){
            j => ((typ(0)(j)) -> small_banks(get_which_res(i, j)).douta.asTypeOf(new SmallBankReadData(w)))
        })
        x1 := MuxCase(0.U.asTypeOf(new SmallBankReadData(w)), Seq.tabulate(4){
            j => ((typ(1)(j+4)) -> small_banks(get_which_res(i, j)+4).douta.asTypeOf(new SmallBankReadData(w)))
        })
        io.rd_small(0)(i) := RegNext(x0, 0.U.asTypeOf(new SmallBankReadData(w)))
        io.rd_small(1)(i) := RegNext(x1, 0.U.asTypeOf(new SmallBankReadData(w)))
    }

    big_banks(1).addrb := io.wr_addr.addrs(0).addr
    big_banks(1).dinb := io.to_bigbank.asUInt
    big_banks(0).addrb := io.wr_addr.addrs(0).addr
    big_banks(0).dinb := io.to_bigbank.asUInt
    when(io.wr_addr.addrs(0).bank_id(0)){
        big_banks(0).web := io.wr_valid_in
    }.otherwise{
        big_banks(1).web := io.wr_valid_in
    }
    for(i <- 0 to 3){
        small_banks(i*2).addrb := io.wr_addr.addrs(1).addr
        small_banks(i*2).dinb := io.to_smallbank(0).asUInt
        small_banks(i*2+1).addrb := io.wr_addr.addrs(2).addr
        small_banks(i*2+1).dinb := io.to_smallbank(1).asUInt
        when(io.wr_addr.addrs(1).bank_id(i*2)){
            small_banks(i*2).web := io.wr_valid_in
            small_banks(i*2+1).web := io.wr_valid_in
        }
    }

}

class ROMBiasBundle(para_num: Int) extends Bundle{
    val addr = Input(UInt(StdPara.bias_addr_w.W))
    val out = Output(Vec(para_num, SInt(StdPara.bias_w.W)))
}

class ROMBias(para_num: Int) extends Module{
    val io = IO(new ROMBiasBundle(para_num))
    val rom = Module(new bias0(para_num*StdPara.bias_w, StdPara.bias_addr_w)).io
    rom.clka := clock
    rom.addra := io.addr
    io.out := rom.douta.asTypeOf(Vec(para_num, SInt(StdPara.bias_w.W)))
}

class ROMWeightBundle(para_num: Int) extends Bundle{
    val addr = Input(UInt(StdPara.weight_addr_w.W))
    val out = Output(Vec(para_num, Vec(9, SInt(16.W))))
}

class ROMWeight(para_num: Int) extends Module{
    val io = IO(new ROMWeightBundle(para_num))
    val rom = Module(new weight0(para_num*StdPara.weight_w, StdPara.weight_addr_w)).io
    rom.clka := clock
    rom.addra := RegNext(io.addr, 0.U)
    io.out := rom.douta.asTypeOf(Vec(para_num, Vec(9, SInt(16.W))))
}

class ROMHalfBias(para_num: Int) extends Module{
    val io = IO(new ROMBiasBundle(para_num))
    val rom = Module(new bias0_2(para_num*StdPara.bias_w, StdPara.bias_addr_w)).io
    rom.clka := clock
    rom.addra := io.addr
    io.out := rom.douta.asTypeOf(Vec(para_num, SInt(StdPara.bias_w.W)))

}

class ROMHalfWeight(para_num: Int) extends Module{
    val io = IO(new ROMWeightBundle(para_num))
    val rom = Module(new weight0_2(para_num*StdPara.weight_w, StdPara.weight_addr_w)).io
    rom.clka := clock
    rom.addra := RegNext(io.addr, 0.U)
    io.out := rom.douta.asTypeOf(Vec(para_num, Vec(9, SInt(16.W))))
}

class big_bank1(w: Int, addr_w: Int) extends BlackBox{
    val io = IO(new TrueDualPort(w, addr_w))
}


package final_project

import chisel3._
import chisel3.util._
import chisel3.util.HasBlackBoxInline

class AccumuRawData(val w: Int) extends Bundle{
    val mat = Vec(64, SInt((w*2+10).W))
}

class AccumuBundle(w: Int, addr_w: Int, bias_w: Int, para_num: Int) extends Bundle{
    val in_from_calc8x8 = Input(Vec(para_num, new RawData(w)))
    val result = Output(Vec(para_num, new AccumuRawData(w)))
    val csum = Input(UInt(addr_w.W))
    val bias_begin_addr = Input(UInt(addr_w.W))
    val bias_end_addr = Input(UInt(addr_w.W))
    val bias_addr = Output(UInt(addr_w.W))
    val bias_in = Input(Vec(para_num, SInt(bias_w.W)))
    val is_in_use = Input(Bool())
    val valid_in = Input(UInt(para_num.W))
    val valid_out = Output(UInt(para_num.W))
    val flag_job = Input(Bool())
}

// for x.. for y.. for ic.. for oc..
// get the sum of a 8x8 block

class Accumu(w: Int, addr_w: Int, bias_w: Int, para_num: Int) extends Module{
    val io = IO(new AccumuBundle(w, addr_w, bias_w, para_num))

    val counter = RegInit(0.U.asTypeOf(ACounter(addr_w)))
    val now_addr = RegInit(0.U.asTypeOf(RCounter(addr_w)))
    val enable = RegInit(false.B)
    val valid_out_reg = RegInit(0.U(para_num.W))

    io.result := 0.U.asTypeOf(Vec(para_num, new AccumuRawData(w)))
    io.valid_out := 0.U
    io.bias_addr := now_addr.ccnt

    val r64 = RegInit(0.U.asTypeOf(Vec(para_num, new AccumuRawData(w))))

    for(t <- 0 to para_num-1)
        for(i <- 0 to 63){
            val adder = Module(new DSP48Adder).io
            adder.a := io.in_from_calc8x8(t).mat(i)
            adder.b := r64(t).mat(i)
            adder.t := io.bias_in(t)
            adder.flag := counter.ccnt===counter.cend
            when(io.valid_in(0)){
                r64(t).mat(i) := adder.c
            }
        }
    io.result := r64
    when(io.flag_job){
        counter.set(io.csum, io.csum)
        now_addr.set(io.bias_begin_addr, io.bias_end_addr)
        r64 := 0.U.asTypeOf(r64)
        enable := io.is_in_use
        valid_out_reg := 0.U
    }.elsewhen(enable){
        when(io.valid_in(0)){

                valid_out_reg := 0.U
                counter.ccnt := counter.ccnt-1.U
            }
            when(counter.ccnt===1.U){
                now_addr.inc()
            }
        }.otherwise{
            valid_out_reg := 0.U
        }
        io.valid_out := valid_out_reg
    }.otherwise{
        io.result(0) := io.in_from_calc8x8(0)
        io.valid_out := io.valid_in
    }

}

class DSP48Adder extends BlackBox with HasBlackBoxInline{
    val io = IO(new Bundle{
        val a = Input(SInt(36.W))
        val t = Input(SInt(36.W))
        val flag = Input(Bool())
        val b = Input(SInt(42.W))
        val c = Output(SInt(42.W))
    })
    setInline(
        "BlackBoxAdder3642.v",
        """(*use_dsp="yes"*)
        |module DSP48Adder(
        |    input[35:0] a,
        |    input[35:0] t,
        |    input flag,
        |    input[41:0] b,
        |    output[41:0] c
        |);
        |wire[41:0] x;
        |assign x = flag? {{6{t[35]}}, t}: b;
        |assign c = x + $signed({{6{a[35]}}, a});
        |endmodule
        """.stripMargin)
}

            when(counter.ccnt===counter.cend){
                valid_out_reg := 0.U
                counter.ccnt := counter.ccnt-1.U
            }.elsewhen(counter.ccnt===0.U){


package final_project

import chisel3._
import chisel3.util._
import chisel3.experimental.ChiselEnum

class FirstTransInput(val w: Int) extends Bundle{
    val mat_real = Vec(36, SInt(w.W))
    val mat_comp = Vec(12, SInt(w.W))
}

class FinalTransInput(val w: Int) extends Bundle{
    val mat_real = Vec(36, SInt(w.W))
    val mat_comp = Vec(10, SInt(w.W))
}

class TransOutput(val w: Int) extends Bundle{
    val mat_real = Vec(36, SInt(w.W))
    val mat_comp = Vec(36, SInt(w.W))
}

class FirstTransOutput(val w: Int) extends Bundle{
    val mat_real = Vec(24, SInt(w.W))
    val mat_comp = Vec(24, SInt(w.W))
}

class CalcWeightData extends Bundle{
    val real = Vec(16, SInt(16.W))
}

class WeightData extends Bundle{
    val real = Vec(16, SInt(StdPara.dsp_w.W))
    val sbx = Vec(10, SInt(StdPara.dsp_w.W))
    val rbx = Vec(10, Bool())
    val bx = Vec(10, SInt(StdPara.dsp_w.W))
    val by = Vec(10, SInt(StdPara.dsp_w.W))
}

class InputData(w: Int) extends Bundle {
    val mat = Vec(36, SInt(w.W))
}

class OutputData(val w: Int) extends Bundle {
    val mat = Vec(16, SInt((w*2+6).W))
}

object CalcType extends ChiselEnum {
    val empty  = Value(0.U)
    val leakyReLU = Value(1.U)
    val calcMult = Value(2.U)
    val calcConv = Value(3.U)
}

class Calc6x6(val w: Int, val para_num: Int) extends Module{
    val io = IO(new Bundle{
        val input = Input(new InputData(w))
        val flag = Input(CalcType())
        val weight = Input(Vec(para_num, new WeightData()))
        val output = Output(Vec(para_num, new OutputData(w)))
        val valid_in = Input(Bool())
        val valid_out = Output(Bool())
    })

    io.output := 0.U.asTypeOf(io.output)
    io.valid_out := false.B

    val real = Array.fill(para_num, 16)(Module(new Core()).io)

    for(t <- 0 to para_num-1)
        for(i <- 0 to 15){
            real(t)(i).w_a := 0.U.asTypeOf(real(t)(i).w_a)
            real(t)(i).in_b := 0.U.asTypeOf(real(t)(i).in_b)
        }

    val comp = Array.fill(para_num, 10)(Module(new ComplexCore()).io)

    for(t <- 0 to para_num-1)
        for(i <- 0 to 9){
            comp(t)(i).sbx := 0.S
            comp(t)(i).rbx := false.B
            comp(t)(i).bx := 0.S
            comp(t)(i).by := 0.S
            comp(t)(i).ax := 0.S
            comp(t)(i).ay := 0.S
        }

    val reg1 = RegInit(0.U.asTypeOf(new FirstTransInput(w+2)))
    val reg2 = RegInit(0.U.asTypeOf(new FinalTransInput(w+4)))
    val w3 = RegInit(VecInit(Seq.fill(para_num)(0.U.asTypeOf(new TransOutput(w+StdPara.dsp_w+4)))))
    val reg3 = RegInit(VecInit(Seq.fill(para_num)(0.U.asTypeOf(new FirstTransOutput(w+StdPara.dsp_w+5)))))

    def g6(x: Int, y: Int): Int = {
        return x*6+y
    }
    def g4(x: Int, y: Int): Int = {
        return x*4+y
    }

    def carry_save(A: SInt, B: SInt, C: SInt): SInt = {
        val _A = Wire(SInt((A.getWidth+1).W))
        val _B = Wire(SInt((A.getWidth+1).W))
        val _C = Wire(SInt((A.getWidth+1).W))
        _A := A
        _B := B
        _C := C
        val r_and = Wire(SInt((A.getWidth+1).W))
        val r_xor = Wire(SInt((A.getWidth+1).W))
        r_and := (_A&(_B|_C))|(_B&_C)
        r_xor := _A^_B^_C
        return (r_and<<1.U)+&r_xor
    }

    def first_trans_input(in: InputData): Unit = {
        for(i <- 0 to 5){

            val w2m4 = RegInit(0.S((in.mat(g6(2, i)).getWidth+1).W))
            val w2a4 = RegInit(0.S((in.mat(g6(2, i)).getWidth+1).W))
            val w1a3 = RegInit(0.S((in.mat(g6(1, i)).getWidth+1).W))

            w2m4 := in.mat(g6(4, i))-&in.mat(g6(2, i))
            w2a4 := in.mat(g6(2, i))+&in.mat(g6(4, i))

            w1a3 := in.mat(g6(1, i))+&in.mat(g6(3, i))

            reg1.mat_real(g6(0, i)) := RegNext(in.mat(g6(0, i))-&in.mat(g6(4, i)), 0.S)
            reg1.mat_real(g6(1, i)) := w1a3+&w2a4
            reg1.mat_real(g6(2, i)) := w2a4-&w1a3
            reg1.mat_real(g6(3, i)) := w2m4
            reg1.mat_real(g6(4, i)) := w2m4
            reg1.mat_real(g6(5, i)) := RegNext(in.mat(g6(5, i))-&in.mat(g6(1, i)), 0.S)

            val w1m3 = RegInit(0.S((in.mat(g6(1, i)).getWidth+1).W))
            w1m3 := in.mat(g6(1, i))-&in.mat(g6(3, i))

            reg1.mat_comp(g6(0, i)) := -w1m3  // g(3, i, 6)
            reg1.mat_comp(g6(1, i)) := w1m3  // g(4, i, 6)
        }
    }

    def final_trans_input(in: FirstTransInput): Unit = {
        for(i <- 0 to 5){

            val w13 = RegInit(0.S((in.mat_real(g6(i, 1)).getWidth+1).W))
            val w24 = RegInit(0.S((in.mat_real(g6(i, 2)).getWidth+1).W))
            val w2m4 = RegInit(0.S((in.mat_real(g6(i, 2)).getWidth+1).W))

            w13 := in.mat_real(g6(i, 1))+&in.mat_real(g6(i, 3))
            w24 := in.mat_real(g6(i, 2))+&in.mat_real(g6(i, 4))
            w2m4 := in.mat_real(g6(i, 4))-&in.mat_real(g6(i, 2))
            if(i>=3&&i<=4){
                val c13 = RegInit(0.S((in.mat_comp(g6(1, 3)).getWidth+1).W))
                if(i==3){
                    c13 := in.mat_comp(g6(1, 1))-&in.mat_comp(g6(1, 3))
                } else {
                    c13 := in.mat_comp(g6(1, 3))-&in.mat_comp(g6(1, 1))
                }

                reg2.mat_real(g6(i, 3)) := w2m4-&c13
                reg2.mat_real(g6(i, 4)) := w2m4+&c13
            } else {
                reg2.mat_real(g6(i, 3)) := w2m4
                reg2.mat_real(g6(i, 4)) := w2m4
            }

            reg2.mat_real(g6(i, 0)) := RegNext(in.mat_real(g6(i, 0))-&in.mat_real(g6(i, 4)), 0.S)
            reg2.mat_real(g6(i, 1)) := w13+&w24
            reg2.mat_real(g6(i, 2)) := w24-&w13
            reg2.mat_real(g6(i, 5)) := RegNext(in.mat_real(g6(i, 5))-&in.mat_real(g6(i, 1)), 0.S)
        }
        val w13 = RegInit(0.S((in.mat_real(g6(0, 1)).getWidth+1).W))
        val w33 = RegInit(0.S((in.mat_real(g6(0, 1)).getWidth+1).W))
        val w24 = RegInit(0.S((in.mat_real(g6(0, 2)).getWidth+1).W))
        val w2m4 = RegInit(0.S((in.mat_real(g6(0, 2)).getWidth+1).W))

        w13 := in.mat_comp(g6(0, 1))+&in.mat_comp(g6(0, 3))
        w24 := in.mat_comp(g6(0, 2))+&in.mat_comp(g6(0, 4))
        w2m4 := in.mat_comp(g6(0, 4))-&in.mat_comp(g6(0, 2))
        w33 := in.mat_real(g6(3, 1))-&in.mat_real(g6(3, 3))

        //(3, 0...5)
        reg2.mat_comp(0) := RegNext(in.mat_comp(g6(0, 0))-&in.mat_comp(g6(0, 4)), 0.S)
        reg2.mat_comp(1) := w13+&w24
        reg2.mat_comp(2) := w24-&w13
        reg2.mat_comp(3) := w2m4-&w33
        reg2.mat_comp(4) := w2m4+&w33
        reg2.mat_comp(5) := RegNext(in.mat_comp(g6(0, 5))-&in.mat_comp(g6(0, 1)), 0.S)

        // (0, 3) (1, 3) (2, 3) (5, 3)
        reg2.mat_comp(6) := RegNext(in.mat_real(g6(0, 3))-&in.mat_real(g6(0, 1)), 0.S)
        reg2.mat_comp(7) := RegNext(in.mat_real(g6(1, 3))-&in.mat_real(g6(1, 1)), 0.S)
        reg2.mat_comp(8) := RegNext(in.mat_real(g6(2, 3))-&in.mat_real(g6(2, 1)), 0.S)
        reg2.mat_comp(9) := RegNext(in.mat_real(g6(5, 3))-&in.mat_real(g6(5, 1)), 0.S)
    }

    def first_trans_output(out: FirstTransOutput, in: TransOutput): Unit = {
        for(i <- 0 to 5)if(i!=4){

            val w1a2 = RegInit(0.S((in.mat_real(g6(1, i)).getWidth+1).W))
            val w1m2 = RegInit(0.S((in.mat_real(g6(1, i)).getWidth+1).W))

            w1a2 := in.mat_real(g6(1, i))+&in.mat_real(g6(2, i))
            w1m2 := in.mat_real(g6(1, i))-&in.mat_real(g6(2, i))

            val r34 = RegInit(0.S((in.mat_real(g6(3, i)).getWidth+1).W))
            val c34 = RegInit(0.S((in.mat_comp(g6(3, i)).getWidth+1).W))

            if(i==3){
                r34 := in.mat_real(g6(3, 3))+&in.mat_real(g6(3, 4))
                c34 := in.mat_comp(g6(3, 3))+&in.mat_comp(g6(3, 4))
            } else {
                r34 := in.mat_real(g6(3, i))<<1.U
                c34 := in.mat_comp(g6(3, i))<<1.U
            }
            val r_mat_0_i = RegInit(0.S((in.mat_real(g6(0, i)).getWidth+2).W))
            val r_mat_5_i = RegInit(0.S((in.mat_comp(g6(5, i)).getWidth+2).W))

            r_mat_0_i := in.mat_real(g6(0, i))<<(2.U)
            r_mat_5_i := in.mat_real(g6(5, i))<<(2.U)

            out.mat_real(g6(0, i)) := carry_save(w1a2, r34, r_mat_0_i)>>2.U
            out.mat_real(g6(1, i)) := (w1m2-&c34)>>2.U
            out.mat_real(g6(2, i)) := (w1a2-&r34)>>2.U
            out.mat_real(g6(3, i)) := carry_save(w1m2, c34, r_mat_5_i)>>2.U

        }

        val _w1a2 = RegInit(0.S((in.mat_real(g6(1, 3)).getWidth+1).W))
        val _w1m2 = RegInit(0.S((in.mat_real(g6(1, 3)).getWidth+1).W))

        _w1a2 := in.mat_comp(g6(1, 3))+&in.mat_comp(g6(2, 3))
        _w1m2 := in.mat_comp(g6(1, 3))-&in.mat_comp(g6(2, 3))

        val _r34 = RegInit(0.S((in.mat_real(g6(3, 3)).getWidth+1).W))
        val _c34 = RegInit(0.S((in.mat_comp(g6(3, 3)).getWidth+1).W))

        _r34 := in.mat_real(g6(3, 4))-&in.mat_real(g6(3, 3))
        _c34 := in.mat_comp(g6(3, 3))-&in.mat_comp(g6(3, 4))

        val c_mat_0_3 = RegInit(0.S((in.mat_real(g6(0, 3)).getWidth+2).W))
        val c_mat_5_3 = RegInit(0.S((in.mat_comp(g6(5, 3)).getWidth+2).W))

        c_mat_0_3 := in.mat_comp(g6(0, 3))<<(2.U)
        c_mat_5_3 := in.mat_comp(g6(5, 3))<<(2.U)

        out.mat_comp(g6(0, 3)) := carry_save(_w1a2, _c34, c_mat_0_3)>>2.U
        out.mat_comp(g6(1, 3)) := (_w1m2-&_r34)>>2.U
        out.mat_comp(g6(2, 3)) := (_w1a2-&_c34)>>2.U
        out.mat_comp(g6(3, 3)) := carry_save(_w1m2, _r34, c_mat_5_3)>>2.U

    }

    def final_trans_output(output: OutputData, in: FirstTransOutput): Unit = {
        for(i <- 0 to 3){
            val w1a2 = RegInit(0.S((in.mat_real(g6(i, 1)).getWidth).W))
            val w1m2 = RegInit(0.S((in.mat_real(g6(i, 1)).getWidth).W))

            w1a2 := (in.mat_real(g6(i, 1))+&in.mat_real(g6(i, 2)))>>1.U
            w1m2 := (in.mat_real(g6(i, 1))-&in.mat_real(g6(i, 2)))>>1.U

            val r34 = RegInit(0.S((in.mat_real(g6(i, 3)).getWidth).W))
            val c34 = RegInit(0.S((in.mat_comp(g6(i, 3)).getWidth).W))

            r34 := in.mat_real(g6(i, 3))
            c34 := in.mat_comp(g6(i, 3))

            val r_mat_0_i = RegInit(0.S((in.mat_real(g6(i, 0)).getWidth+1).W))
            val r_mat_5_i = RegInit(0.S((in.mat_real(g6(i, 5)).getWidth+1).W))

            r_mat_0_i := in.mat_real(g6(i, 0))<<(1.U)
            r_mat_5_i := in.mat_real(g6(i, 5))<<(1.U)

            output.mat(g4(i, 0)) := RegNext(carry_save(w1a2, r34, r_mat_0_i)>>1.U, 0.S)
            output.mat(g4(i, 1)) := RegNext(((w1m2-&c34)>>1.U), 0.S)
            output.mat(g4(i, 2)) := RegNext(((w1a2-&r34)>>1.U), 0.S)
            output.mat(g4(i, 3)) := RegNext(carry_save(w1m2, c34, r_mat_5_i)>>1.U, 0.S)
        }
    }

    val valid_reg = RegInit(VecInit(Seq.fill(13)(false.B)))

    // first_trans_input(io.input)
    first_trans_input(RegNext(RegNext(io.input, 0.U.asTypeOf(io.input)), 0.U.asTypeOf(io.input)))
    final_trans_input(reg1)
    for(t <- 0 to para_num-1)
        first_trans_output(reg3(t), w3(t))
    valid_reg(1) := valid_reg(0)
    valid_reg(2) := valid_reg(1)
    valid_reg(3) := valid_reg(2)
    valid_reg(4) := valid_reg(3)
    valid_reg(5) := valid_reg(4)
    valid_reg(6) := valid_reg(5)
    valid_reg(7) := valid_reg(6)
    valid_reg(8) := valid_reg(7)
    valid_reg(9) := valid_reg(8)
    valid_reg(10) := valid_reg(9)
    valid_reg(11) := valid_reg(10)
    valid_reg(12) := valid_reg(11)

    for(t <- 0 to para_num-1){
        var y = 0
        for(i <- 0 to 5){
            for(j <- 0 to 5){
                if(i==3){
                    comp(t)(y).sbx := io.weight(t).sbx(y)
                    comp(t)(y).rbx := io.weight(t).rbx(y)
                    comp(t)(y).bx := io.weight(t).bx(y)
                    comp(t)(y).by := io.weight(t).by(y)
                    comp(t)(y).ax := reg2.mat_real(g6(i, j))
                    comp(t)(y).ay := reg2.mat_comp(j)

                    w3(t).mat_real(g6(i, j)) := comp(t)(y).x
                    w3(t).mat_comp(g6(i, j)) := comp(t)(y).y

                    y = y+1
                } else if(i!=4&&j==3){

                    comp(t)(y).sbx := io.weight(t).sbx(y)
                    comp(t)(y).rbx := io.weight(t).rbx(y)
                    comp(t)(y).bx := io.weight(t).bx(y)
                    comp(t)(y).by := io.weight(t).by(y)
                    comp(t)(y).ax := reg2.mat_real(g6(i, j))
                    comp(t)(y).ay := reg2.mat_comp(real_i)

                    w3(t).mat_real(g6(i, j)) := comp(t)(y).x
                    w3(t).mat_comp(g6(i, j)) := comp(t)(y).y

                    y = y+1
                }
            }
        }
    }
    switch(io.flag){
        is(CalcType.leakyReLU){
            for(i <- 0 to 15){
                real(0)(i).w_a := Mux(io.input.mat((i/4)*6+(i%4))(w-1), 6554.S, 32768.S)
                real(0)(i).in_b := io.input.mat((i/4)*6+(i%4))
                io.output(0).mat(i) := real(0)(i).result
            }
            io.valid_out := RegNext(RegNext(io.valid_in, false.B), false.B)
        }

        is(CalcType.calcMult){
            for(i <- 0 to 15){
                real(0)(i).in_b := io.input.mat((i/4)*6+(i%4))
                real(0)(i).w_a := io.weight(0).real(i)
                io.output(0).mat(i) := real(0)(i).result
            }

            io.valid_out := RegNext(RegNext(io.valid_in, false.B), false.B)
        }

        is(CalcType.calcConv){
            valid_reg(0) := io.valid_in
            for(t <- 0 to para_num-1)
                final_trans_output(io.output(t), reg3(t))

            for(t <- 0 to para_num-1){
                var x = 0
                var y = 0
                for(i <- 0 to 5){
                    for(j <- 0 to 5){
                        if(i!=3&&i!=4&&j!=3&&j!=4){
                            real(t)(x).in_b := reg2.mat_real(g6(i, j))
                            real(t)(x).w_a := io.weight(t).real(x)
                            w3(t).mat_real(g6(i, j)) := real(t)(x).result
                            x = x+1
                        }
                    }
                }
            }
            io.valid_out := valid_reg(12)
            // io.valid_out := valid_reg(10)

        }
    }
}


package final_project

import chisel3._
import chisel3.util._

class PackedData(val w: Int) extends Bundle {
    val mat = Vec(64, SInt(w.W))
    val up = Vec(10, SInt(w.W))
    val down = Vec(10, SInt(w.W))
    val left = Vec(8, SInt(w.W))
    val right = Vec(8, SInt(w.W))
}

class RawData(val w: Int) extends Bundle {
    val mat = Vec(64, SInt((w*2+4).W))
}

class Calc8x8(val w: Int, val para_num: Int) extends Module{
    val io = IO(new Bundle{
        val input = Input(new PackedData(w))
        val flag = Input(CalcType())
        val mask = Input(UInt(para_num.W))
        val weight = Input(Vec(para_num, new CalcWeightData()))
        val multmap = Input(Vec(4, new CalcWeightData()))
        val output = Output(Vec(para_num, new RawData(w)))
        val valid_in = Input(Bool())
        val valid_out = Output(UInt(para_num.W))
    })
    def get_one(a: PackedData, x: Int, y: Int): Data = {
        if(x==0) return a.up(y)
        if(x==9) return a.down(y)
        if(y==0) return a.left(x-1)
        if(y==9) return a.right(x-1)
        return a.mat((x-1)*8+(y-1))
    }
    def get_area(a: PackedData, lx: Int, ly: Int, rx: Int, ry: Int): Data = {
        val ret = Wire(new InputData(w))
        for(i <- 0 to 5)
            for(j <- 0 to 5)
                ret.mat(i*6+j) := (if(i<rx-lx+1&&j<ry-ly+1) get_one(a, i+lx, j+ly) else 0.S)
        return ret
    }

    def set_output(a0: OutputData, a1: OutputData, a2: OutputData, a3: OutputData): Data = {
        val ret = Wire(new RawData(w))
        for(i <- 0 to 3)
            for(j <- 0 to 3)
                ret.mat(i*8+j) := a0.mat(i*4+j)
        for(i <- 0 to 3)
            for(j <- 4 to 7)
                ret.mat(i*8+j) := a1.mat(i*4+(j-4))
        for(i <- 4 to 7)
            for(j <- 0 to 3)
                ret.mat(i*8+j) := a2.mat((i-4)*4+j)
        for(i <- 4 to 7)
            for(j <- 4 to 7)
                ret.mat(i*8+j) := a3.mat((i-4)*4+(j-4))
        return ret
    }
    io.output := 0.U.asTypeOf(io.output)
    io.valid_out := 0.U

    val A = VecInit(Seq.fill(4)(Module(new Calc6x6(w, para_num)).io))
    for(i <- 0 to 3){
        A(i).input := 0.U.asTypeOf(A(i).input)
        A(i).flag := 0.U.asTypeOf(A(i).flag)
        A(i).weight := 0.U.asTypeOf(A(i).weight)
        A(i).valid_in := false.B
    }

    val B = Wire(Vec(para_num, Vec(3, Vec(3, SInt(16.W)))))
    val _B = RegInit(0.U.asTypeOf(Vec(para_num, Vec(6, Vec(3, SInt(18.W))))))
    val _Bi = RegInit(0.U.asTypeOf(Vec(para_num, Vec(6, Vec(3, SInt(18.W))))))

    for(t <- 0 to para_num-1)
        for(i <- 0 to 8)
            B(t)(i/3)(i%3) := io.weight(t).real(i)

    for(t <- 0 to para_num-1)
        for(j <- 0 to 2){
            val w0m2 = RegNext(B(t)(0)(j)-&B(t)(2)(j), 0.S)
            val w0a2 = RegNext(B(t)(0)(j)+&B(t)(2)(j), 0.S)

            val b_t_1_j = RegNext(B(t)(1)(j), 0.S)

            _B(t)(0)(j) := RegNext(B(t)(0)(j), 0.S)
            _B(t)(1)(j) := w0a2+&b_t_1_j
            _B(t)(2)(j) := w0a2-&b_t_1_j
            _B(t)(3)(j) := w0m2
            // _B(t)(4)(j) := w0m2
            _B(t)(5)(j) := RegNext(B(t)(2)(j), 0.S)
            _Bi(t)(3)(j) := b_t_1_j
            // _Bi(t)(4)(j) := -b_t_1_j
        }

    val __B = RegInit(0.U.asTypeOf(Vec(para_num, Vec(6, Vec(6, SInt(20.W))))))
    val __Bi = RegInit(0.U.asTypeOf(Vec(para_num, Vec(6, Vec(6, SInt(20.W))))))

    for(t <- 0 to para_num-1)
        for(i <- 0 to 5)if(i!=4){

            val w0m2 = RegNext(_B(t)(i)(0)-&_B(t)(i)(2), 0.S)
            val w0a2 =RegNext(_B(t)(i)(0)+&_B(t)(i)(2), 0.S)

            val b_t_i_1 = RegNext(_B(t)(i)(1), 0.S)
            val bi_t_i_1 = RegNext(_Bi(t)(i)(1), 0.S)

            __B(t)(i)(0) := RegNext(_B(t)(i)(0), 0.S)
            __B(t)(i)(1) := w0a2+&b_t_i_1
            __B(t)(i)(2) := w0a2-&b_t_i_1
            __B(t)(i)(3) := w0m2-&bi_t_i_1
            if(i==3)
                __B(t)(i)(4) := w0m2+&bi_t_i_1
            __B(t)(i)(5) := RegNext(_B(t)(i)(2), 0.S)

            val cw0m2 = RegNext(_Bi(t)(i)(0)-&_Bi(t)(i)(2), 0.S)
            val cw0a2 = RegNext(_Bi(t)(i)(0)+&_Bi(t)(i)(2), 0.S)

            __Bi(t)(i)(3) := cw0m2+&b_t_i_1
            if(i==3){
                __Bi(t)(i)(0) := RegNext(_Bi(t)(i)(0), 0.S)
                __Bi(t)(i)(1) := cw0a2+&bi_t_i_1
                __Bi(t)(i)(2) := cw0a2-&bi_t_i_1
                __Bi(t)(i)(4) := cw0m2-&b_t_i_1
                __Bi(t)(i)(5) := RegNext(_Bi(t)(i)(2), 0.S)
            }

        }

    // val conv_weight = Wire(Vec(para_num, new WeightData()))
    val conv_weight = RegInit(0.U.asTypeOf(Vec(para_num, new WeightData())))
    // val conv_weight = RegInit(0.U.asTypeOf(new WeightData()))

    def clamp_18(x: SInt): SInt = {
        // return x
        val xx = StdPara.dsp_w
        val r = ((1<<(xx-1))-1).S(20.W)
        val l = (-(1<<(xx-1))).S(20.W)
        return Mux(x>=r, r, Mux(x<=l, l, x))
        //return Mux((~x(18))&&x(17), ((1<<17)-1).S(18.W), Mux(x(18)&&(~x(17)), (-(1<<17)).S(18.W), x))
    }

    for(t <- 0 to para_num-1){
        var x = 0
        var y = 0
        for(i <- 0 to 5){
            for(j <- 0 to 5){
                if(i!=3&&i!=4&&j!=3&&j!=4){
                    conv_weight(t).real(x) := RegNext(clamp_18(__B(t)(i)(j)), 0.S(StdPara.dsp_w.W))
                    // conv_weight(t).real(x) := clamp_18(__B(t)(i)(j))
                    x = x+1
                } else if(i==3||(i!=4&&j==3)){
                    val __Bx = RegInit(0.S(StdPara.dsp_w.W))
                    val __Bix = RegInit(0.S(StdPara.dsp_w.W))
                    // val __Bx = Wire(SInt(StdPara.dsp_w.W))
                    // val __Bix = Wire(SInt(StdPara.dsp_w.W))
                    __Bx := clamp_18(__B(t)(i)(j))
                    __Bix := clamp_18(__Bi(t)(i)(j))
                    conv_weight(t).sbx(y) := ((__Bx+&__Bix)>>1.U)
                    conv_weight(t).rbx(y) := __Bx(0)^__Bix(0)
                    conv_weight(t).bx(y) := __Bx
                    conv_weight(t).by(y) := __Bix
                    y = y+1
                }
            }
        }
    }

    switch(io.flag){
        is(CalcType.leakyReLU){
            A(0).input := get_area(io.input, 1, 1, 4, 4)
            A(1).input := get_area(io.input, 1, 5, 4, 8)
            A(2).input := get_area(io.input, 5, 1, 8, 4)
            A(3).input := get_area(io.input, 5, 5, 8, 8)
            for(i <- 0 to 3){
                A(i).flag := CalcType.leakyReLU
                A(i).valid_in := io.valid_in
            }
            io.output(0) := set_output(A(0).output(0), A(1).output(0), A(2).output(0), A(3).output(0))
            io.valid_out := (if(para_num==1) A(0).valid_out else Cat(0.U((para_num-1).W), A(0).valid_out))
        }
        is(CalcType.calcMult){
            A(0).input := get_area(io.input, 1, 1, 4, 4)
            A(1).input := get_area(io.input, 1, 5, 4, 8)
            A(2).input := get_area(io.input, 5, 1, 8, 4)
            A(3).input := get_area(io.input, 5, 5, 8, 8)
            for(i <- 0 to 3){
                A(i).weight(0).real := io.multmap(i).real
                A(i).flag := CalcType.calcMult
                A(i).valid_in := io.valid_in
            }
            io.output(0) := set_output(A(0).output(0), A(1).output(0), A(2).output(0), A(3).output(0))

        }
        is(CalcType.calcConv){
            A(0).input := get_area(io.input, 0, 0, 5, 5)
            A(1).input := get_area(io.input, 0, 4, 5, 9)
            A(2).input := get_area(io.input, 4, 0, 9, 5)
            A(3).input := get_area(io.input, 4, 4, 9, 9)
            for(i <- 0 to 3){
                A(i).weight := conv_weight
                A(i).flag := CalcType.calcConv
                A(i).valid_in := io.valid_in
            }
            for(t <- 0 to para_num-1)
                io.output(t) := set_output(A(0).output(t), A(1).output(t), A(2).output(t), A(3).output(t))
            io.valid_out := Cat(Seq.tabulate(para_num){
                i => A(0).valid_out&io.mask(para_num-1-i)
            })
        }

        is(CalcType.empty){
            io.valid_out := io.valid_in
            for(i <- 0 to 63)
                io.output(0).mat(i) := io.input.mat(i)
        }
    }
}


package final_project

import chisel3._
import chisel3.util._
import chisel3.experimental.ChiselEnum

class Core extends Module{
    val io = IO(new Bundle{
        val w_a = Input(SInt(StdPara.dsp_w.W))
        val in_b = Input(SInt(20.W))

}

class DSP48 extends Module{
    val io = IO(new Bundle{
        val in_a = Input(SInt(StdPara.dsp_w.W))
        val in_b = Input(SInt(20.W))
        val out = Output(SInt(40.W))
    })
    /*
    val M = Module(new ip_dsp48).io
    M.A := io.in_b
    M.B := io.in_a
    io.out := M.P*/
    io.out := io.in_a*io.in_b
}

class ComplexCore extends Module{
    val io = IO(new Bundle{
        val sbx = Input(SInt(StdPara.dsp_w.W))
        val rbx = Input(Bool())
        val bx = Input(SInt(StdPara.dsp_w.W))
        val by = Input(SInt(StdPara.dsp_w.W))
        val ax = Input(SInt(20.W))
        val ay = Input(SInt(20.W))
        val x = Output(SInt(40.W))
        val y = Output(SInt(40.W))
    })
    val _ax = RegNext(io.ax, 0.S(20.W))
    val _ay = RegNext(io.ay, 0.S(20.W))
    val _bx = RegNext(io.bx, 0.S(StdPara.dsp_w.W))
    val _by = RegNext(io.by, 0.S(StdPara.dsp_w.W))
    val w = RegNext((io.ay<<(1.U))*io.sbx+&Mux(io.rbx, io.ay, 0.S), 0.S)
    io.x := RegNext((_ax+&_ay)*_bx-&w, 0.S)
    io.y := RegNext((_ax-&_ay)*_by+&w, 0.S)
}

        val result = Output(SInt(40.W))
    })

    val dsp48 = Module(new DSP48())


package final_project

import chisel3._
import chisel3.util._

class QuantedData(val w: Int) extends Bundle{
    val mat = Vec(64, SInt(w.W))
}

class QuantBundle(val w: Int) extends TransBundle{
    val in_from_accumu = Input(new AccumuRawData(w))
    val result = Output(new QuantedData(w))
    val quant_in = Input(UInt(5.W))
}

class Quant(val w: Int) extends Module{
    val io = IO(new QuantBundle(w))

    val r_q = RegInit(0.U(5.W))

    io.valid_out := false.B
    io.result := 0.U.asTypeOf(new QuantedData(w))

        return Mux(ret1<=ret3, ret3, Mux(ret1>=ret2, ret2, ret1))
    }

    val mat = RegInit(0.U.asTypeOf(new AccumuRawData(w)))
    mat := io.in_from_accumu
    for(i <- 0 to 63)
        io.result.mat(i) := clamp_rs(mat.mat(i), r_q)

    when(io.flag_job){
        r_q := io.quant_in
    }.otherwise{
        io.valid_out := RegNext(io.valid_in, false.B)
    }

}


package final_project

import chisel3._
import chisel3.util._
import chisel3.experimental.ChiselEnum

object ReadSwitchType extends ChiselEnum{
    val idle = Value(0.U)
    val toConv = Value(1.U)
    val toMult2 = Value(2.U)
    val toMaxp = Value(3.U)
    val toCopy = Value(4.U)
    val toUps = Value(5.U)
}

class ReadSwitchBundle(val w: Int, val h_w: Int, val c_w: Int, val para_num: Int) extends Bundle{
    val flag_job = Input(Bool())
    val job = Input(ReadSwitchType())
    val in_h = Input(UInt(h_w.W))
    val in_chan = Input(UInt(c_w.W))
    val valid_in = Input(Bool())
    val valid_out_calc8x8 = Output(Bool())
    val valid_out_copy = Output(Bool())
    val valid_out_maxp = Output(Bool())
    val from = Input(new PackedData(w))
    val from_weight = Input(Vec(para_num, Vec(9, SInt(16.W))))
    val to_calc8x8 = Output(new PackedData(w))
    val to_copy = Output(new QuantedData(w))
    val to_weight = Output(Vec(para_num, new CalcWeightData()))
    val to_multmap = Output(Vec(4, new CalcWeightData()))

    val valid_in_from_user = Input(Bool())
    val data_from_user = Input(new QuantedData(w))
}

class ReadSwitch(val w: Int, val h_w: Int, val c_w: Int, val para_num: Int) extends Module{
    val io = IO(new ReadSwitchBundle(w, h_w, c_w, para_num))
    val job_type = RegInit(0.U.asTypeOf(ReadSwitchType()))

    val state = RegInit(false.B)

    val cnt_h = RegInit(0.U.asTypeOf(ACounter(h_w.W)))
    val cnt_ic = RegInit(0.U.asTypeOf(ACounter(c_w.W)))
    val ups_state = RegInit(false.B)

    io.valid_out_calc8x8 := false.B
    io.valid_out_copy := false.B
    io.valid_out_maxp := false.B
    io.to_calc8x8 := 0.U.asTypeOf(io.to_calc8x8)
    io.to_weight := 0.U.asTypeOf(io.to_weight)
    io.to_copy := 0.U.asTypeOf(io.to_copy)
    io.to_multmap := 0.U.asTypeOf(io.to_multmap)

    io.to_calc8x8 := io.from
    cache := io.from.mat
    for(ii <- 0 to 1)
        for(jj <- 0 to 1)
            for(i <- 0 to 3)
                for(j <- 0 to 3)
                    io.to_multmap(ii*2+jj).real(i*4+j) := cache((i+ii*4)*8+(j+jj*4))

    for(t <- 0 to para_num-1)
        for(i <- 0 to 8)
            io.to_weight(t).real(i) := io.from_weight(t)(i)
    when(io.flag_job){
        job_type := io.job
        ups_state := false.B
        cnt_h.set(io.in_h)
        cnt_ic.set(io.in_chan)
        state := false.B
    }.otherwise{
        switch(job_type){
            is(ReadSwitchType.toConv){
                io.valid_out_calc8x8 := io.valid_in
            }
            is(ReadSwitchType.toCopy){
                io.valid_out_copy := io.valid_in
                io.to_copy.mat := io.from.mat
            }
            is(ReadSwitchType.toMult2){
                when(io.valid_in){
                    when(state){
                        io.valid_out_calc8x8 := true.B
                    }
                    state := ~state
                }
            }
            is(ReadSwitchType.toMaxp){
                io.valid_out_maxp := io.valid_in
            }
            is(ReadSwitchType.idle){
                io.valid_out_copy := io.valid_in_from_user
                io.to_copy := io.data_from_user
            }
            is(ReadSwitchType.toUps){
                when(io.valid_in){
                    io.valid_out_copy := true.B
                    when(cnt_ic.inc()){
                        state := ~state
                        when(cnt_h.inc()){
                            ups_state := ~ups_state
                        }
                    }
                }

                when(ups_state){
                    when(state){
                        for(i <- 0 to 7)
                            for(j <- 0 to 7)
                                io.to_copy.mat(i*8+j) := io.from.mat((i/2+4)*8+(j/2+4))
                    }.otherwise{
                        for(i <- 0 to 7)
                            for(j <- 0 to 7)
                                io.to_copy.mat(i*8+j) := io.from.mat((i/2)*8+(j/2+4))
                    }
                }.otherwise{
                    when(state){
                        for(i <- 0 to 7)
                            for(j <- 0 to 7)
                                io.to_copy.mat(i*8+j) := io.from.mat((i/2+4)*8+(j/2))
                    }.otherwise{
                        for(i <- 0 to 7)
                            for(j <- 0 to 7)
                                io.to_copy.mat(i*8+j) := io.from.mat((i/2)*8+(j/2))
                    }
                }
            }
        }
    }
}

class WriteSwitch(val w: Int, val num: Int) extends Module{
    val io = IO(new Bundle{
        val valid_in = Input(Vec(num, new Bool()))
        val valid_out = Output(Bool())
        val input = Input(Vec(num, new QuantedData(w)))
        val output = Output(new QuantedData(w))
    })
    io.output := MuxCase(0.U.asTypeOf(io.output), Seq.tabulate(num){
        i => (io.valid_in(i) -> io.input(i))
    })
    io.valid_out := io.valid_in.reduce(_ | _)
}

val cache = RegInit(VecInit(Seq.fill(64){0.S(w.W)}))


package final_project

import chisel3._
import chisel3.util._

class Maxpool(val w: Int) extends Module{
    val io = IO(new Bundle{
        val from_big = Input(Vec(2, new BigBankReadData(w)))
        val from_small = Input(Vec(2, Vec(2, new SmallBankReadData(w))))
        val valid_in = Input(Bool())
        val valid_out = Output(Bool())
        val result = Output(new QuantedData(w))
    })
    val state = RegInit(0.U(1.W))
    val output = RegInit(VecInit(Seq.fill(32)(0.S(w.W))))
    val cache = Wire(Vec(32, SInt(w.W)))

    cache := 0.U.asTypeOf(cache)

    io.valid_out := false.B
    io.result := 0.U.asTypeOf(io.result)

    for(t <- 0 to 1){
        for(ii <- 0 to 3){
            for(jj <- 0 to 3){
                val w4 = Wire(Vec(4, SInt(w.W)))
                val a2 = Wire(SInt(w.W))
                val b2 = Wire(SInt(w.W))
                for(i <- 0 to 1){
                    for(j <- 0 to 1){
                        val x = ii*2+i
                        val y = jj*2+j
                        if(y==0){
                            w4(i*2+j) := io.from_small(t)(0).data(x)
                        } else if(y==7){
                            w4(i*2+j) := io.from_small(t)(1).data(x)
                        } else {
                            w4(i*2+j) := io.from_big(t).data(x*6+y-1)
                        }
                    }
                }
                a2 := Mux(w4(0)<w4(1), w4(1), w4(0))

    when(io.valid_in){
        switch(state){
            is(0.U){
                output := cache
                state := 1.U
            }
            is(1.U){
                for(i <- 0 to 3)
                    for(j <- 0 to 3)
                        io.result.mat(i*8+j) := output(i*4+j)
                for(i <- 0 to 3)
                    for(j <- 0 to 3)
                        io.result.mat((i+4)*8+j) := output((i+4)*4+j)

                for(i <- 0 to 3)
                    for(j <- 0 to 3)
                        io.result.mat((i)*8+j+4) := cache((i)*4+j)

                for(i <- 0 to 3)
                    for(j <- 0 to 3)
                        io.result.mat((i+4)*8+j+4) := cache((i+4)*4+j)
                io.valid_out := true.B
                state := 0.U
            }
        }
    }
}


package final_project

import chisel3._
import chisel3.util._

import chisel3.experimental.BundleLiterals._

import chisel3.stage.ChiselStage

// test

class Fuck extends Module{
    val io = IO(new Bundle{
        val sbx = Input(SInt(18.W))
        val rbx = Input(SInt(40.W))
        val bx = Input(SInt(18.W))
        val by = Input(SInt(18.W))
        val ax = Input(SInt(20.W))
        val ay = Input(SInt(20.W))
        val x = Output(SInt(40.W))
        val y = Output(SInt(40.W))
    })
    val _ax = RegNext(io.ax, 0.S(18.W))
    val _ay = RegNext(io.ay, 0.S(18.W))
    val _bx = RegNext(io.bx, 0.S(18.W))
    val _by = RegNext(io.by, 0.S(18.W))
    val w = RegInit(0.S(40.W))
    w := (io.ay<<(1.U))*io.sbx+&io.rbx
    io.x := (_ax+&_ay)*_bx-&w
    io.y := (_ax-&_ay)*_by+&w
}
/*
class FuckAdd extends Module{
    val io = IO(new Bundle{
        val t = Input(SInt(48.W))
        val tx = Input(SInt(48.W))
        val tg = Input(Bool())
        val hi_y = Input(SInt(24.W))
        val lo_y = Input(SInt(24.W))
        val hi_out = Output(SInt(24.W))
        val lo_out = Output(SInt(24.W))
    })
    val state = RegInit(0.U(1.W))
    val hi_and = RegInit(0.S(24.W))
    val lo_and = RegInit(0.S(24.W))
    val hi_xor = RegInit(0.S(24.W))
    val lo_xor = RegInit(0.S(24.W))
    val lst_hi = RegInit(VecInit(Seq.fill(2)(0.S(24.W))))
    val lst_lo = RegInit(VecInit(Seq.fill(2)(0.S(24.W))))
    when(tg){
        lo_and := t(23, 0)&tx(23, 0)
        lo_xor := t(23, 0)^tx(23, 0)
        hi_xor := t(47, 24)^tx(47, 24)
        hi_and := t(47, 24)&tx(47, 24)
        lst_hi(0) := t(23, 0)
        lst_lo(0) := t(47, 24)
        lst_hi(1) := tx(23, 0)
        lst_lo(1) := tx(47, 24)
        state := 0.U
    }.otherwise{
        hi_and :=
    }

}
*/
class Accumu2(w: Int, addr_w: Int, bias_w: Int, para_num: Int) extends Module{
    val io = IO(new AccumuBundle(w, addr_w, bias_w, para_num))

    io.result := 0.U.asTypeOf(Vec(para_num, new AccumuRawData(w)))
    io.bias_addr := 0.U
    io.valid_out := io.valid_in

        }
    io.result := r64
}
object FafaDriver extends App {
    (new ChiselStage).emitVerilog(
        new Top,
        //new ROMBias,
        //new RAMGroup(StdPara.w, StdPara.addr_w, StdPara.id_w),
        Array("--target-dir", "C:\\Users\\yfz\\Desktop\\data\\Plan\\homework\\AI_fpga\\final_project\\project_1\\")
    )
}

object ShowDriver extends App {
    (new ChiselStage).emitVerilog(
        new Show,
        //new ROMBias,
        //new RAMGroup(StdPara.w, StdPara.addr_w, StdPara.id_w),
        Array("--target-dir", "C:\\Users\\yfz\\Desktop\\data\\Plan\\homework\\AI_fpga\\final_project\\project_1\\")
    )
}

object FuckDriver extends App {
    (new ChiselStage).emitVerilog(
        new Accumu2(16, 10, 36, 4),
        //new ROMBias,
        //new RAMGroup(StdPara.w, StdPara.addr_w, StdPara.id_w),
        Array("--target-dir", "C:\\Users\\yfz\\Desktop\\data\\Plan\\homework\\AI_fpga\\final_project\\project_test\\")
    )
}

object XDriver extends App {
    (new ChiselStage).emitVerilog(
        new Wrapper,
        Array("--target-dir", "C:\\Users\\yfz\\Desktop\\data\\Plan\\homework\\AI_fpga\\final_project\\project_1\\")
    )
}

    val r64 = RegInit(0.U.asTypeOf(Vec(para_num, new AccumuRawData(w))))
    for(t <- 0 to para_num-1)