// See LICENSE.IBM for license details.

package xfiles.standalone

import chisel3._
import chisel3.util._
import cde._
import xfiles.XFilesUserRequests

abstract class XFilesTests(implicit p: Parameters) extends XFilesTester {
  // New Transaction


// See LICENSE.BU for license details.

package xfiles

import chisel3._
import chisel3.util._

class QueueIOAf[T <: Data](gen: T, entries: Int) extends QueueIO[T](gen, entries) {
  val almostFull = Output(Bool())

}

class QueueAf[T <: Data](gen: T, entries: Int, almostFullEntries: Int,
  pipe: Boolean = false, flow: Boolean = false,
  override_reset: Option[Bool] = None)
    extends Module(override_reset = override_reset) {

  val io = IO(new QueueIOAf(gen, entries))
  val queue = Module(new Queue(gen, entries, pipe, flow, override_reset))

  io.enq <> queue.io.enq
  io.deq <> queue.io.deq
  io.count := queue.io.count
  io.almostFull := queue.io.count >= almostFullEntries.U
}

override def cloneType = new QueueIOAf(gen, entries).asInstanceOf[this.type]


// See LICENSE.BU for license details.
// See LICENSE.IBM for license details.

package dana

import chisel3._
import chisel3.util._

class SRAMInterface(
  val dataWidth: Int,
  val numReadPorts: Int,
  val numWritePorts: Int,
  val numReadWritePorts: Int,
  val sramDepth: Int
) extends Bundle {
  override def cloneType = new SRAMInterface(
    dataWidth = dataWidth,
    numReadPorts = numReadPorts,
    numWritePorts = numWritePorts,
    numReadWritePorts = numReadWritePorts,
    sramDepth = sramDepth
  ).asInstanceOf[this.type]
  // Data Input
  val din   = Input(Vec(numReadWritePorts, UInt(dataWidth.W)))
  val dinW  = Input(Vec(numWritePorts,     UInt(dataWidth.W)))
  // Data Output
  val dout  = Output(Vec(numReadWritePorts, UInt(dataWidth.W)))
  val doutR = Output(Vec(numReadPorts,      UInt(dataWidth.W)))
  // Addresses
  val addr  = Input(Vec(numReadWritePorts, UInt(log2Up(sramDepth).W)))
  val addrR = Input(Vec(numReadPorts,      UInt(log2Up(sramDepth).W)))
  val addrW = Input(Vec(numWritePorts,     UInt(log2Up(sramDepth).W)))
  // Write enable
  val we    = Input(Vec(numReadWritePorts, Bool()))
  val weW   = Input(Vec(numWritePorts,     Bool()))
  // Read enable
  val re    = Input(Vec(numReadWritePorts, Bool()))
  val reR   = Input(Vec(numReadPorts, Bool()))
}

class SRAM (
  val id: Int                = 0,
  val dataWidth: Int         = 8,
  val sramDepth: Int         = 64,
  val numReadPorts: Int      = 0,
  val numWritePorts: Int     = 0,
  val numReadWritePorts: Int = 2,
  val initSwitch: Int        = -1,
  val elementsPerBlock: Int  = -1
) extends Module {
  val io = IO(new SRAMInterface(
    numReadPorts = numReadPorts,
    numWritePorts = numWritePorts,
    numReadWritePorts = numReadWritePorts,
    dataWidth = dataWidth,
    sramDepth = sramDepth))

  val mem = SeqMem(sramDepth, UInt(dataWidth.W))

  for (i <- 0 until numReadWritePorts) {

    when (io.re(i))                    { io.dout(i)       := mem(io.addr(i))  }}

  for (i <- 0 until numReadPorts)      {
    when (io.reR(i))                   { io.doutR(i)      := mem(io.addrR(i)) }}

  for (i <- 0 until numWritePorts)     {
    when (io.weW(i))                   { mem(io.addrW(i)) := io.dinW(i)       }}
}

class SRAMSinglePortInterface(
  val dataWidth: Int,
  val sramDepth: Int
) extends Bundle {
  override def cloneType = new SRAMDualPortInterface(
    dataWidth = dataWidth,
    sramDepth = sramDepth).asInstanceOf[this.type]
  val we = Output(Bool())
  val din = Output(UInt(dataWidth.W))
  val addr = Output(UInt(log2Up(sramDepth).W))
  val dout = Input(UInt(dataWidth.W))
}

class SRAMDualPortInterface(
  val dataWidth: Int,
  val sramDepth: Int
) extends Bundle {
  override def cloneType = new SRAMDualPortInterface(
    dataWidth = dataWidth,
    sramDepth = sramDepth).asInstanceOf[this.type]
  val we = Output(Vec(2, Bool()))
  val din = Output(Vec(2, UInt(dataWidth.W)))
  val addr = Output(Vec(2, UInt(log2Up(sramDepth).W)))
  val dout = Input(Vec(2, UInt(dataWidth.W)))
}

class SRAMDualPort(
  val dataWidth: Int,
  val sramDepth: Int
) extends Module {
  val io = Flipped(new SRAMDualPortInterface(
    dataWidth = dataWidth,
    sramDepth = sramDepth))
  val sram = Module(new SRAM(
    dataWidth = dataWidth,
    numReadPorts = 0,
    numWritePorts = 0,
    numReadWritePorts = 2,
    initSwitch = -1,
    elementsPerBlock = -1,
    sramDepth = sramDepth)).io

  for (i <- 0 until 2) {
    sram.we(i) := io.we(i)
    sram.din(i) := io.din(i)
    sram.addr(i) := io.addr(i)
    io.dout(i) := sram.dout(i)
  }
}

when (io.we(i))                    { mem(io.addr(i))  := io.din(i)        }


// See LICENSE.BU for license details.
// See LICENSE.IBM for license details.

package dana

import chisel3._
import chisel3.util._

// SRAMBlock variant that allows for element _and_ block writes with
// the option of writing a block that is accumualted with the elements
// of the existing block. Forwarding is allowable for all cases.

// This uses both write enable (we) and write type (wType) input
// lines. The write type is defined as follows:
//   0: element write (like SRAMBlock0
//   1: block write overwriting old block
//   2: block write accumulating element-wise with old block

class SRAMBlockIncrementInterface (
  override val dataWidth: Int,
  override val sramDepth: Int,
  override val numPorts: Int,
  val elementWidth: Int
) extends SRAMVariantInterface(dataWidth, sramDepth, numPorts) {
  override def cloneType = new SRAMBlockIncrementInterface(
    dataWidth = dataWidth,
    sramDepth = sramDepth,
    numPorts = numPorts,
    elementWidth = elementWidth).asInstanceOf[this.type]
  val inc = Input(Vec(numPorts, Bool()))
}

class WritePendingBlockIncrementBundle (
  val elementWidth: Int,
  val dataWidth: Int,
  val sramDepth: Int
) extends Bundle {
  override def cloneType = new WritePendingBlockIncrementBundle (
    elementWidth = elementWidth,
    dataWidth = dataWidth,
    sramDepth = sramDepth).asInstanceOf[this.type]
  val valid = Bool()
  val inc = Bool()
  val data = UInt(dataWidth.W)
  val addr = UInt(log2Up(sramDepth).W)
}

// A special instance of the generic SRAM that allows for masked
// writes to the SRAM. Reads happen normally, but writes happen using
// a 2-cyle read-modify-write operation. Due to the nature of this
// operation, each write port needs an associated read port.
// Consequently, this only has RW ports.
class SRAMBlockIncrement (
  override val id: Int = 0,
  override val dataWidth: Int = 32,
  override val sramDepth: Int = 64,
  override val numPorts: Int = 1,
  val elementWidth: Int = 8
) extends SRAMVariant(id, dataWidth, sramDepth, numPorts) {
  override lazy val io = IO(new SRAMBlockIncrementInterface(
    dataWidth = dataWidth,
    sramDepth = sramDepth,
    numPorts = numPorts,
    elementWidth = elementWidth))

  val elementsPerBlock = divUp(dataWidth, elementWidth)

  def writeBlock(a: Vec[UInt], b: UInt) {
    val bTupled = (((x: Int, y: Int) => b.apply(x, y)) tupled)
    (0 until elementsPerBlock).map(j => a(j) := bTupled(index(j))) }
  def writeBlockIncrement(a: Vec[UInt], b: UInt, c: UInt) {
    val bTupled = (((x: Int, y: Int) => b.apply(x, y)) tupled)
    val cTupled = (((x: Int, y: Int) => c.apply(x, y)) tupled)
    (0 until elementsPerBlock).map(j => a(j) :=
      bTupled(index(j)) + cTupled(index(j))) }

  val writePending = Reg(Vec(numPorts, new WritePendingBlockIncrementBundle(
    elementWidth = elementWidth,
    dataWidth = dataWidth,
    sramDepth = sramDepth)))

  val tmp0 = Wire(Vec(numPorts, Vec(elementsPerBlock, UInt(elementWidth.W))))
  val tmp1 = Wire(Vec(numPorts, Vec(elementsPerBlock, UInt(elementWidth.W))))
  val forwarding = Wire(Vec(numPorts, Bool()))

  // Combinational Logic
  for (i <- 0 until numPorts) {
    val fwd = (io.we(i) && writePending(i).valid &&
      io.addr(i) === writePending(i).addr)

    // Connections to the sram
    sram.weW(i) := writePending(i).valid
    sram.dinW(i) := tmp1(i).asUInt
    sram.addrW(i) := writePending(i).addr
    sram.addrR(i) := io.addr(i)
    sram.reR(i) := io.re(i) || (io.we(i) && !fwd)
    io.dout(i) := sram.doutR(i)

    // Defaults
    val doutRTupled = (((x: Int, y: Int) => sram.doutR(i)(x, y)) tupled)
    (0 until elementsPerBlock).map(j => tmp0(i)(j) := doutRTupled(index(j)))
    tmp1(i) := tmp0(i)
    forwarding(i) := fwd

    // Handle a pending write if one exists
    when (writePending(i).valid) {
      when (!writePending(i).inc) {
        writeBlock(tmp0(i), writePending(i).data)
      } .otherwise {
        writeBlockIncrement(tmp0(i), writePending(i).data, sram.doutR(i)) }

      // Deal with forwarding
      when (forwarding(i)) {
        when (!io.inc(i)) {
          writeBlock(tmp0(i), io.din(i))
        } .otherwise {
          writeBlockIncrement(tmp1(i), tmp0(i).asUInt, io.din(i)) }}

      printf("[INFO] SramBloInc: WRITE port/inc?/fwd?/fwdInc? 0x%x/0x%x/0x%x/0x%x\n",
        i.U, writePending(i).inc, forwarding(i), io.inc(i))
      printf("[INFO]              DATA addr/dataOld/dataNew 0x%x/0x%x/0x%x\n",
        writePending(i).addr, sram.doutR(i), tmp1(i).asUInt) }}

  // Sequential Logic
  for (i <- 0 until numPorts) {
    // Assign the pending write data
    writePending(i).valid := false.B
    when ((io.we(i)) && (forwarding(i) === false.B)) {
      writePending(i).valid := true.B
      writePending(i).inc := io.inc(i)
      writePending(i).data := io.din(i)
      writePending(i).addr := io.addr(i) }}
}

def index(j: Int): (Int, Int) = (elementWidth*(j+1) - 1, elementWidth * j)


// See LICENSE.BU for license details.
// See LICENSE.IBM for license details.

package dana

import chisel3._
import chisel3.util._

// [TODO] This module is currently non-working. It was an initial
// solution to the problem of dealing with random writes from the
// Processing Elements and needing to maintain a record of which
// entries were valid. Knowledge of valid entries is necessary to
// ensure that subsequent PE reads are only reading valid data. This
// approach introduces metadata into each SRAM block that contains the
// number of valid entries in that block.

class SRAMElementCounterResp (
  val sramDepth: Int
) extends Bundle {

}

class SRAMElementCounterInterface (
  val dataWidth: Int,
  val sramDepth: Int,
  val numPorts: Int,
  val elementWidth: Int
) extends Bundle {
  override def cloneType = new SRAMElementInterface(
    dataWidth = dataWidth,
    sramDepth = sramDepth,
    numPorts = numPorts,
    elementWidth = elementWidth).asInstanceOf[this.type]
  val we = Output(Vec(numPorts, Bool()))
  val din = Output(Vec(numPorts, UInt(elementWidth.W)))
  val addr = Output(Vec(numPorts, UInt(log2Up(sramDepth * dataWidth / elementWidth).W)))
  val dout = Input(Vec(numPorts, UInt(dataWidth.W)))
  // lastBlock sets which is the last block in the SRAM
  val lastBlock = Input(Vec(numPorts, UInt(log2Up(sramDepth).W)))
  // lastCount sets the number of elements in the last block
  val lastCount = Input(Vec(numPorts, UInt((log2Up(dataWidth / elementWidth) + 1).W)))
  val resp = Vec(numPorts, Decoupled(new SRAMElementCounterResp (
    sramDepth = sramDepth)) )
}

// write (i.e., SRAMElement), but also includes a count of the number
// of valid elements in each block. When a block is determined to be
// completely valid, this module generates an output update signal
// indicating which block is now valid.
class SRAMElementCounter (
  val dataWidth: Int = 32,
  val sramDepth: Int = 64,
  val elementWidth: Int = 8,
  val numPorts: Int = 1
) extends Module {
  val io = IO(Flipped(new SRAMElementCounterInterface(
    dataWidth = dataWidth,
    sramDepth = sramDepth,
    numPorts = numPorts,
    elementWidth = elementWidth
  )))
  val sram = Module(new SRAM(
    dataWidth = dataWidth + log2Up(dataWidth / elementWidth) + 1,
    sramDepth = sramDepth,
    numReadPorts = numPorts,
    numWritePorts = numPorts,
    numReadWritePorts = 0
  ))

  val addr = Vec(numPorts, new Bundle{
    val addrHi = UInt(log2Up(sramDepth).W)
    val addrLo = UInt(log2Up(dataWidth / elementWidth).W)})

  val writePending = Reg(Vec(numPorts, new WritePendingBundle(
    elementWidth = elementWidth,
    dataWidth = dataWidth,
    sramDepth = sramDepth)))

  val tmp = Vec(numPorts, Vec(dataWidth/elementWidth, UInt(elementWidth.W)))
  val count = Vec(numPorts, UInt((log2Up(dataWidth / elementWidth) + 1).W))
  val forwarding = Vec(numPorts, Bool())

  // Combinational Logic
  for (i <- 0 until numPorts) {
    // Assign the addresses
    addr(i).addrHi := io.addr(i)(
      log2Up(sramDepth * dataWidth / elementWidth) - 1,
      log2Up(dataWidth / elementWidth))
    addr(i).addrLo := io.addr(i)(
      log2Up(dataWidth / elementWidth) - 1, 0)
    // Connections to the sram
    sram.io.weW(i) := writePending(i).valid
    // Explicit data and count assignments
    sram.io.dinW(i) := 0.U
    sram.io.dinW(i)(sramDepth - 1, 0) := tmp(i)
    sram.io.dinW(i)(sramDepth + log2Up(dataWidth/elementWidth) + 1 - 1) :=
      count(i) + 1.U + forwarding(i)
    sram.io.addrR(i) := addr(i).addrHi
    io.dout(i) := sram.io.doutR(i)(dataWidth - 1, 0)
    // Defaults
    io.resp(i).valid := false.B
    io.resp(i).bits.index := 0.U
    forwarding(i) := false.B
    tmp(i) := sram.io.doutR(i)(dataWidth - 1, 0)
    count(i) := sram.io.doutR(i)(
      dataWidth + log2Up(dataWidth/elementWidth) + 1 - 1, dataWidth)
    sram.io.addrW(i) := writePending(i).addrHi
    when (writePending(i).valid) {
      for (j <- 0 until dataWidth / elementWidth) {
        when (j.U === writePending(i).addrLo) {
          tmp(i)(j) := writePending(i).data
        } .elsewhen(addr(i).addrHi === writePending(i).addrHi &&
            io.we(i) &&
            j.U === addr(i).addrLo) {
          tmp(i)(j) := io.din(i)
          forwarding(i) := true.B
        } .otherwise {
          tmp(i)(j) := sram.io.doutR(i)((j+1) * elementWidth - 1, j * elementWidth)
        }
      }
      // Generate a response if we've filled up an entry. An entry is
      // full if it's count is equal to the number of elementsPerBlock
      // or if it's the last count in the last block (this covers the
      // case of a partially filled last block).
      when (count(i) + 1.U + forwarding(i) === (dataWidth / elementWidth).U ||
        (count(i) + 1.U + forwarding(i) === io.lastCount(i) &&
          writePending(i).addrHi === io.lastBlock(i))) {
        io.resp(i).valid := true.B
        io.resp(i).bits.index := writePending(i).addrHi
        sram.io.dinW(i)(sramDepth + log2Up(dataWidth/elementWidth) + 1 - 1) := 0.U
      }
    }
  }

  // Sequential Logic
  for (i <- 0 until numPorts) {
    // Assign the pending write data
    writePending(i).valid := false.B
    when ((io.we(i)) && (forwarding(i) === false.B)) {
      writePending(i).valid := true.B
      writePending(i).data := io.din(i)
      writePending(i).addrHi := addr(i).addrHi
      writePending(i).addrLo := addr(i).addrLo
    }
  }

  // Assertions
  assert(isPow2(dataWidth / elementWidth),
    "dataWidth/elementWidth must be a power of 2")
}

  override def cloneType = new SRAMElementCounterResp (
    sramDepth = sramDepth).asInstanceOf[this.type]


// See LICENSE.BU for license details.
// See LICENSE.IBM for license details.

package dana

import chisel3._
import chisel3.util._

// SRAMElement variant that allows for element _and_ block writes with
// the option of writing a block that is accumualted with the elements
// of the existing block. Forwarding is allowable for all cases.

// This uses both write enable (we) and write type (wType) input
// lines. The write type is defined as follows:
//   0: element write (like SRAMElement0
//   1: block write overwriting old block
//   2: block write accumulating element-wise with old block

class SRAMElementIncrementInterface (
  override val dataWidth: Int,
  override val sramDepth: Int,
  override val numPorts: Int,
  override val elementWidth: Int
) extends SRAMElementInterface(dataWidth, sramDepth, numPorts, elementWidth) {
  override def cloneType = new SRAMElementIncrementInterface(
    dataWidth = dataWidth,
    sramDepth = sramDepth,
    numPorts = numPorts,
    elementWidth = elementWidth).asInstanceOf[this.type]
  val wType = Input(Vec(numPorts, UInt(log2Up(3).W)))
}

class WritePendingIncrementBundle (
  val elementWidth: Int,
  val dataWidth: Int,
  val sramDepth: Int
) extends Bundle {
  override def cloneType = new WritePendingIncrementBundle (
    elementWidth = elementWidth,
    dataWidth = dataWidth,
    sramDepth = sramDepth).asInstanceOf[this.type]
  val valid       = Bool()
  val wType       = UInt(log2Up(3).W)
  val dataElement = UInt(elementWidth.W)
  val dataBlock   = UInt(dataWidth.W)
  val addrHi      = UInt(log2Up(sramDepth).W)
  val addrLo      = UInt(log2Up(dataWidth / elementWidth).W)
}

// A special instance of the generic SRAM that allows for masked
// writes to the SRAM. Reads happen normally, but writes happen using
// a 2-cyle read-modify-write operation. Due to the nature of this
// operation, each write port needs an associated read port.
// Consequently, this only has RW ports.
class SRAMElementIncrement (
  override val id: Int = 0,
  override val dataWidth: Int = 32,
  override val sramDepth: Int = 64,
  override val numPorts: Int = 1,
  val elementWidth: Int = 8
) extends SRAMVariant(id, dataWidth, sramDepth, numPorts) {
  override lazy val io = IO(new SRAMElementIncrementInterface(
    dataWidth = dataWidth,
    sramDepth = sramDepth,
    numPorts = numPorts,
    elementWidth = elementWidth
  ))

  val elementsPerBlock = divUp(dataWidth, elementWidth)

  def writeBlock(a: Vec[UInt], b: UInt) {
    val bTupled = (((x: Int, y: Int) => b.apply(x, y)) tupled)
    (0 until elementsPerBlock).map(j => a(j) := bTupled(index(j))) }
  def writeBlockIncrement(a: Vec[UInt], b: UInt, c: UInt) {
    val bTupled = (((x: Int, y: Int) => b.apply(x, y)) tupled)
    val cTupled = (((x: Int, y: Int) => c.apply(x, y)) tupled)
    (0 until elementsPerBlock).map(j => a(j) :=
      bTupled(index(j)) + cTupled(index(j))) }

  class AddrBundle(
    val sramDepth: Int,
    val elementsPerBlock: Int
  ) extends Bundle {
    val addrHi = UInt(log2Up(sramDepth).W)
    val addrLo = UInt(log2Up(elementsPerBlock).W)
    override def cloneType = new AddrBundle(
      sramDepth = sramDepth,
      elementsPerBlock = elementsPerBlock).asInstanceOf[this.type]
  }

  val addr = Wire(Vec(numPorts, new AddrBundle(sramDepth,elementsPerBlock)))

  val writePending = Reg(Vec(numPorts, new WritePendingIncrementBundle(
    elementWidth = elementWidth,
    dataWidth = dataWidth,
    sramDepth = sramDepth)))

  val tmp0 = Wire(Vec(numPorts,Vec(elementsPerBlock,UInt(elementWidth.W))))
  val tmp1 = Wire(Vec(numPorts,Vec(elementsPerBlock,UInt(elementWidth.W))))
  val forwarding = Wire(Vec(numPorts, Bool()))

  // Combinational Logic
  for (i <- 0 until numPorts) {
    // Assign the addresses: addrHi->block address, addrLo->element address
    addr(i).addrHi := io.addr(i)(
      log2Up(sramDepth * elementsPerBlock) - 1, log2Up(elementsPerBlock))
    addr(i).addrLo := io.addr(i)(log2Up(elementsPerBlock) - 1, 0)

    val fwd = (io.we(i) && writePending(i).valid &&
      addr(i).addrHi === writePending(i).addrHi)

    // Connections to the sram
    sram.weW(i) := writePending(i).valid
    sram.reR(i) := io.re(i) || (io.we(i) && !fwd)
    sram.dinW(i) := tmp1(i).asUInt
    sram.addrW(i) := writePending(i).addrHi
    sram.addrR(i) := addr(i).addrHi
    io.dout(i) := sram.doutR(i)

    // Defaults
    val doutRTupled = (((x: Int, y: Int) => sram.doutR(i)(x, y)) tupled)
    (0 until elementsPerBlock).map(j => tmp0(i)(j) := doutRTupled(index(j)))
    tmp1(i) := tmp0(i)
    forwarding(i) := fwd

    // Deal with a pending write if one exists
    when (writePending(i).valid) {
      // Write pending data to tmp0
      switch (writePending(i).wType) {
        is (0.U) {
          writeElement(tmp0(i), writePending(i).addrLo, writePending(i).dataElement) }
        is (1.U) {
          writeBlock(tmp0(i), writePending(i).dataBlock) }
        is (2.U) {
          writeBlockIncrement(tmp0(i), sram.doutR(i), writePending(i).dataBlock) }}

      // Handle forwarding by updating tmp1
      when (forwarding(i)) {
        switch (io.wType(i)) {
          is (0.U) {
            writeElement(tmp1(i), addr(i).addrLo, io.dinElement(i)) }
          is (1.U) {
            writeBlock(tmp1(i), io.din(i)) }
          is (2.U) {
            writeBlockIncrement(tmp1(i), tmp0(i).asUInt, io.din(i)) }}}

      printf("[INFO] SramEleInc: WRITE port/type/fwd?/fwdType 0x%x/0x%x/0x%x/0x%x\n",
        i.U, writePending(i).wType, forwarding(i), io.wType(i))
      printf("[INFO]              DATA addr/dataOld/dataNew 0x%x/0x%x/0x%x\n",
        writePending(i).addrHi##writePending(i).addrLo, sram.doutR(i),
        tmp1(i).asUInt)
    }
  }

  // Sequential Logic
  for (i <- 0 until numPorts) {
    // Assign the pending write data
    writePending(i).valid := false.B
    when ((io.we(i)) && (forwarding(i) === false.B)) {
      writePending(i).valid := true.B
      writePending(i).wType := io.wType(i)
      writePending(i).dataElement := io.dinElement(i)
      writePending(i).dataBlock := io.din(i)
      writePending(i).addrHi := addr(i).addrHi
      writePending(i).addrLo := addr(i).addrLo
    }
  }

  // Assertions

  // We only define write types up through 2
  assert(!Vec((0 until numPorts).map(i =>
    io.we(i) && io.wType(i) > 2.U)).contains(true.B),
    "SRAMElementIncrement saw unsupported wType > 2")
}


/*
* Filename : IF_ID_Register.scala
* Date     : 02-01-2019
* Author   : SunnyChen
*
* Pipeline register between IF stage and ID stage
* */

package riscv_mini_five_stage

import chisel3._

class IF_ID_Registerio extends Bundle with Config {
  val if_pc       = Input(UInt(WLEN.W))
  val if_inst     = Input(UInt(WLEN.W))
  val if_id_write = Input(UInt(IF_ID_WRITE_SIG_LEN.W))
  val if_flush    = Input(UInt(IF_ID_FLUSH_SIG_LEN.W))
  val if_pc_4     = Input(UInt(WLEN.W))
  val id_rs1      = Output(UInt(REG_LEN.W))
  val id_rs2      = Output(UInt(REG_LEN.W))

}

class IF_ID_Register extends Module with Config {
  val io = IO(new IF_ID_Registerio)

  val pc_reg    = RegInit(0.U(WLEN.W))
  val inst_reg  = RegInit(0.U(WLEN.W))
  val pc_4_reg  = RegInit(0.U(WLEN.W))

  pc_reg := Mux(io.if_flush.toBool(), 0.U(WLEN.W),
    Mux(io.if_id_write.toBool(), io.if_pc, pc_reg))

  inst_reg := Mux(io.if_flush.toBool(), 0.U(WLEN.W),
    Mux(io.if_id_write.toBool(), io.if_inst, inst_reg))

  pc_4_reg := Mux(io.if_flush.toBool(), 0.U(WLEN.W),
    Mux(io.if_id_write.toBool(), io.if_pc_4, pc_4_reg))

  io.id_pc    := pc_reg
  io.id_pc_4  := pc_4_reg
  io.id_inst  := inst_reg
  io.id_rs1   := inst_reg(19,15)
  io.id_rs2   := inst_reg(24,20)
}

object IF_ID_Register extends App {
  chisel3.Driver.execute(args, () => new IF_ID_Register)
}

  val id_pc       = Output(UInt(WLEN.W))
  val id_pc_4     = Output(UInt(WLEN.W))


/*
* Filename : InstCache.scala
* Date     : 02-01-2019
* Author   : SunnyChen
*
* Instruction cache for my processor, I treat it as a combinational logic
* 02-01-2019 - test cache, 128B, 32 Words, implemented use Mem
* 17-01-2019 - test cache, 256B, 64 Words, implemented use Mem
* */

package riscv_mini_five_stage

import chisel3._
import chisel3.util.Cat
import chisel3.util.experimental.loadMemoryFromFile

class InstCacheio extends Bundle with Config {
  val addr = Input(UInt(WLEN.W))
  val inst = Output(UInt(WLEN.W))
}

/*
* Instruction cache is a ROM
* This is just a test cache which has a very small size ROM
* */
class InstCache extends Module with Config {
  val io = IO(new InstCacheio)

  // INST_CACHE_LEN Byte and INST_CACHE_LEN / 4 Words
  val cache = Mem(INST_CACHE_LEN, UInt(BLEN.W))

  // Instructions details in "instructions_test.exe"
  loadMemoryFromFile(cache, "initmem/instcache.txt")

  io.inst := Cat(cache(io.addr), cache(io.addr + 1.U), cache(io.addr + 2.U), cache(io.addr + 3.U))
}

object InstCache extends App {
  chisel3.Driver.execute(args, () => new InstCache)
}


/*
* Filename : PC.scala
* Date     : 02-01-2019
* Author   : SunnyChen
*
* Program Counter for my processor
* */

package riscv_mini_five_stage

import chisel3._

class PCio extends Bundle with Config {
  val addr_input  = Input(UInt(WLEN.W))
  val pc_write    = Input(UInt(PC_WRITE_SIG_LEN.W))
  val pc_out      = Output(UInt(WLEN.W))
}

abstract class PC_template extends Module with Config {
  val io = IO(new PCio)
}

class PC extends PC_template {
  val PC_reg = RegInit(0.U(WLEN.W))

  PC_reg := Mux(io.pc_write.toBool(), io.addr_input, PC_reg)
  io.pc_out := PC_reg
}

object PC extends App {
  chisel3.Driver.execute(args, () => new PC)
}


package riscv_mini_five_stage

import chisel3._
import chisel3.iotesters
import chisel3.iotesters.PeekPokeTester
import chisel3.util
import org.scalatest.{FlatSpec, Matchers}

class ALU_Test(c: ALU) extends PeekPokeTester(c) {
  var src_a = 10
  var src_b = 20
  var aluop = 0

  for(i <- 0 to 3) {
    poke(c.io.Src_A, src_a)
    poke(c.io.Src_B, src_b)
    poke(c.io.ALUOp, aluop)
    peek(c.io.Sum)
    peek(c.io.Conflag)
    step(1)
    aluop = aluop + 1
  }
}

class ALU_Spce extends FlatSpec with Matchers {
  iotesters.Driver.execute((Array("--backend-name", "verilator")), () => new ALU) {
    c => new ALU_Test(c)
  }

  iotesters.Driver.execute((Array("--is-verbose")), () => new ALU) {
    c => new ALU_Test(c)
  }


package riscv_mini_five_stage

import chisel3._
import chisel3.util._
import chisel3.iotesters._
import org.scalatest.{FlatSpec, Matchers}

class Addr_Buffer_Test(c: Addr_Buffer) extends PeekPokeTester(c) {
  var addr = 10
  var record = 1
  for(i <- 1 to 20) {
    if((i > 3 && i < 8) || (i > 10)) {
      record = 0
    }
    if(i == 5){
      poke(c.io.flush, 1)
    }else {
      poke(c.io.flush, 0)
    }

    addr = addr + 1

  }
}

class Addr_Buffer_Spec extends FlatSpec with Matchers {
  iotesters.Driver.execute(Array("--backend-name", "verilator"), () => new Addr_Buffer) {
    c => new Addr_Buffer_Test(c)
  }

  iotesters.Driver.execute(Array("--is-verbose"), () => new Addr_Buffer) {
    c => new Addr_Buffer_Test(c)
  }
}

    poke(c.io.addr_input, addr)
    poke(c.io.record, record)
    peek(c.io.front)


package riscv_mini_five_stage

import chisel3._
import chisel3.iotesters._
import chisel3.util
import org.scalatest.{FlatSpec, Matchers}

class Datapath_Test(c: Datapath) extends PeekPokeTester(c) {
  var rs2_out = 10
  var imm = Integer.parseInt("0000001D",16)
  var pc  = Integer.parseInt("0000010E", 16)
  var alu_src = 0

  poke(c.io.ex_datapathio.ex_rs2_out, rs2_out)
  poke(c.io.ex_datapathio.ex_imm, imm)
  poke(c.io.ex_datapathio.ex_pc, pc)
  poke(c.io.ex_datapathio.ex_ALU_Src, alu_src)
  peek(c.io.ex_datapathio.alu_b_src)
  step(1)
}

class Datapath_Spec extends FlatSpec with Matchers {

  iotesters.Driver.execute(Array("--backend-name", "verilator"), () => new Datapath) {
    c => new Datapath_Test(c)
  }


package riscv_mini_five_stage

import chisel3._
import chisel3.iotesters
import chisel3.iotesters.PeekPokeTester
import chisel3.util._
import org.scalatest.{FlatSpec, Matchers}
import Control._

class ID_EX_Register_Test(c: ID_EX_Register) extends PeekPokeTester(c) {
  // Some test code
}

class ID_EX_Register_Spec extends FlatSpec with Matchers {

    c => new ID_EX_Register_Test(c)
  }
}

  iotesters.Driver.execute(Array("--backend-name", "-verilator"), () => new ID_EX_Register) {
    c => new ID_EX_Register_Test(c)
  }


package riscv_mini_five_stage

import chisel3._
import chisel3.iotesters
import chisel3.iotesters.PeekPokeTester
import chisel3.util._
import org.scalatest.{FlatSpec, Matchers}
import Control._

class ImmGen_Test(c: ImmGen) extends PeekPokeTester(c) {
  var inst = Integer.parseInt("00000110000000001010000110000011", 2)
  var imm_sel = IMM_I
  poke(c.io.inst_in, inst)
  poke(c.io.imm_sel, imm_sel)
  peek(c.io.imm)
}

class ImmGen_Spec extends FlatSpec with Matchers {
  iotesters.Driver.execute(Array("--backend-name", "verilator"), () => new ImmGen) {
    c => new ImmGen_Test(c)
  }

  iotesters.Driver.execute(Array("--is-verbose"), () => new ImmGen) {
    c => new ImmGen_Test(c)
  }


package riscv_mini_five_stage

import chisel3._
import chisel3.iotesters._
import chisel3.util
import org.scalatest.{FlatSpec, Matchers}

class RegFile_Test(c: RegFile) extends PeekPokeTester(c) {
  var rs1 = 1
  var rs2 = 2
  var rd = 0
  var wdata = 10

    if(i == 6) {
      rs1 = rd
      rs2 = rd
    }

    poke(c.io.rs1, rs1)
    poke(c.io.rs2, rs2)
    poke(c.io.rd, rd)
    poke(c.io.wdata, wdata)
    poke(c.io.regwrite, 1)
    step(1)
    rd = rd + 1
    wdata = wdata + 1
  }
}

class RegFile_Spec extends FlatSpec with Matchers {
  iotesters.Driver.execute(Array("--backend-name", "verilator"), () => new RegFile) {
    c => new RegFile_Test(c)
  }

  iotesters.Driver.execute(Array("--is-verbose"), () => new RegFile) {
    c => new RegFile_Test(c)
  }
}


package riscv_mini_five_stage

import chisel3._
import chisel3.iotesters
import chisel3.iotesters.PeekPokeTester
import chisel3.util
import org.scalatest.{FlatSpec, Matchers}

class Tile_Test(c: Tile) extends PeekPokeTester(c) {
  for(i <- 0 to 2000) {
    // Input
    peek(c.io.if_pc_out)
    peek(c.io.if_next_pc)
    peek(c.io.id_rs1)
    peek(c.io.id_rs2)
    peek(c.io.id_rs1_out)
    peek(c.io.id_rs2_out)
    peek(c.io.ex_rs1_out)
    peek(c.io.ex_rs2_out)
    peek(c.io.ex_alu_sum)
    peek(c.io.ex_alu_conflag)
    peek(c.io.ex_rd)
    peek(c.io.mem_rd)
    peek(c.io.mem_alu_sum)
    peek(c.io.mem_writedata)
    peek(c.io.mem_dataout)
    peek(c.io.wb_rd)
    peek(c.io.wb_registerwrite)
    peek(c.io.Forward_A)
    peek(c.io.Forward_B)
    peek(c.io.MemWrite_Src)
    step(1)
  }
}

class Tile_Spec extends FlatSpec with Matchers {
  Encoding.generate_hexcode()

  iotesters.Driver.execute(Array("--backend-name", "verilator"), () => new Tile) {
    c => new Tile_Test(c)
  }

  iotesters.Driver.execute(Array("--is-verbose"), () => new Tile){
    c => new Tile_Test(c)
  }


def apply(Pipelined: Boolean = false): InvMixColumns = Module(new InvMixColumns(Pipelined))


def apply(Pipelined: Boolean = false): MixColumns = Module(new MixColumns(Pipelined))


// See README.md for license details.

package gcd

import chisel3._

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

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

  when(x > y) { x := x - y }

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

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


package lfsr

import chisel3._
import chisel3.util.Cat

class LFSR extends Module {
  //declare input-output interface signals
  val io = IO(new Bundle {
    //clock and reset are default,
    //no other inputs necessary
    //lfsr_6 and lfsr_3r will have the random values
    val lfsr_6 = Output(UInt(6.W))
    val lfsr_3r = Output(UInt(3.W))
  })

  //declare the 6-bit register and initialize to 000001
  val D0123456 = RegInit(1.U(6.W)) //will init at reset

  //next clk value is XOR of 2 MSBs as LSB concatenated with left shift rest
  //example: 010010 => 100101 = Cat('10010','0^1')
  val nxt_D0123456 = Cat(D0123456(4, 0), D0123456(5) ^ D0123456(4))

  //update 6-bit register will happen in sync with clk
  D0123456 := nxt_D0123456

  //assign outputs
  io.lfsr_6 := D0123456
  //lfsr_3r in reverse order just for fun
  io.lfsr_3r := Cat(D0123456(1), D0123456(3), D0123456(5))
}

object LFSR {


package aes

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

class AddRoundKeyUnitTester(c: AddRoundKey, Pipelined: Boolean) extends PeekPokeTester(c) {

  def computeAddRoundKey(state_in: Array[Int], roundKey: Array[Int]): Array[Int] = {

    var state_out = new Array[Int](Params.StateLength)

    for (i <- 0 until Params.StateLength) {
      state_out(i) = state_in(i) ^ roundKey(i)
    }

    state_out
  }

  private val aes_ark = c
  var state = Array(0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34)
  var roundKey = Array(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f)

  for (i <- 0 until Params.StateLength) {
    poke(aes_ark.io.state_in(i), state(i))
    poke(aes_ark.io.roundKey(i), roundKey(i))
  }

  state = computeAddRoundKey(state, roundKey)
  println(state.deep.mkString(" "))

  if (Pipelined) {
    step(2)
  } else {
    step(1)
  }

  for (i <- 0 until Params.StateLength)
    expect(aes_ark.io.state_out(i), state(i))
}

// Run test with:
// sbt 'testOnly aes.AddRoundKeyTester'
// extend with the option '-- -z verbose' or '-- -z vcd' for specific test

class AddRoundKeyTester extends ChiselFlatSpec {

  private val Pipelined = true // [false -> 127 LUTs, true -> 64 LUTs and 128 FFs]
  private val backendNames = Array("firrtl", "verilator")
  private val dir = "AddRoundKey"

  for (backendName <- backendNames) {
    "AddRoundKey" should s"execute AES AddRoundKey (with $backendName)" in {
      Driver(() => new AddRoundKey(Pipelined), backendName) {
        c => new AddRoundKeyUnitTester(c, Pipelined)
      } should be(true)
    }
  }

  "Basic test using Driver.execute" should "be used as an alternative way to run specification" in {
    iotesters.Driver.execute(
      Array("--target-dir", "test_run_dir/" + dir + "_basic_test", "--top-name", dir), () => new AddRoundKey(Pipelined)) {
      c => new AddRoundKeyUnitTester(c, Pipelined)
    } should be(true)
  }

  "using --backend-name verilator" should "be an alternative way to run using verilator" in {

          "--backend-name", "verilator"), () => new AddRoundKey(Pipelined)) {
        c => new AddRoundKeyUnitTester(c, Pipelined)
      } should be(true)
    }
  }

  "using --backend-name firrtl" should "be an alternative way to run using firrtl" in {
    if (backendNames.contains("firrtl")) {
      iotesters.Driver.execute(
        Array("--target-dir", "test_run_dir/" + dir + "_firrtl_test", "--top-name", dir,
          "--backend-name", "firrtl", "--generate-vcd-output", "on"), () => new AddRoundKey(Pipelined)) {
        c => new AddRoundKeyUnitTester(c, Pipelined)
      } should be(true)
    }
  }

  "running with --is-verbose" should "show more about what's going on in your tester" in {
    iotesters.Driver.execute(
      Array("--target-dir", "test_run_dir/" + dir + "_verbose_test", "--top-name", dir,
        "--is-verbose"), () => new AddRoundKey(Pipelined)) {
      c => new AddRoundKeyUnitTester(c, Pipelined)
    } should be(true)
  }

}

    if (backendNames.contains("verilator")) {
      iotesters.Driver.execute(


package aes

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

class ShiftRowsUnitTester(c: ShiftRows) extends PeekPokeTester(c) {

  // ShiftRows in Scala
  def computeShiftRows(state_in: Array[Int]): Array[Int] = {
    var state_out = new Array[Int](Params.StateLength)

    state_out(0) = state_in(0)
    state_out(1) = state_in(5)
    state_out(2) = state_in(10)
    state_out(3) = state_in(15)

    state_out(4) = state_in(4)
    state_out(5) = state_in(9)
    state_out(6) = state_in(14)
    state_out(7) = state_in(3)

    state_out(8) = state_in(8)
    state_out(9) = state_in(13)
    state_out(10) = state_in(2)
    state_out(11) = state_in(7)

    state_out(12) = state_in(12)
    state_out(13) = state_in(1)
    state_out(14) = state_in(6)
    state_out(15) = state_in(11)

    state_out
  }

  private val aes_sr = c
  var state = Array(0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34)

  for (i <- 0 until Params.StateLength)
    poke(aes_sr.io.state_in(i), state(i))

  // run in Scala
  state = computeShiftRows(state)
  println(state.deep.mkString(" "))

  step(1)

  // match chisel and Scala
  for (i <- 0 until Params.StateLength)
    expect(aes_sr.io.state_out(i), state(i))
}

// Run test with:
//    sbt 'testOnly aes.ShiftRowsTester'
// or sbt 'testOnly aes.ShiftRowsTester -- -z verbose'
// or sbt 'testOnly aes.ShiftRowsTester -- -z vcd'

class ShiftRowsTester extends ChiselFlatSpec {

  private val backendNames = Array("firrtl", "verilator")
  private val dir = "ShiftRows"

  for (backendName <- backendNames) {
    "ShiftRows" should s"execute AES ShiftRows (with $backendName)" in {
      Driver(() => new ShiftRows, backendName) {
        c => new ShiftRowsUnitTester(c)
      } should be(true)
    }
  }

  "Basic test using Driver.execute" should "be used as an alternative way to run specification" in {
    iotesters.Driver.execute(
      Array("--target-dir", "test_run_dir/" + dir + "_basic_test", "--top-name", dir), () => new ShiftRows) {
      c => new ShiftRowsUnitTester(c)
    } should be(true)
  }

  "using --backend-name verilator" should "be an alternative way to run using verilator" in {
    if (backendNames.contains("verilator")) {
      iotesters.Driver.execute(
        Array("--target-dir", "test_run_dir/" + dir + "_verilator_test", "--top-name", dir,
          "--backend-name", "verilator"), () => new ShiftRows) {
        c => new ShiftRowsUnitTester(c)
      } should be(true)
    }
  }

  "using --backend-name firrtl" should "be an alternative way to run using firrtl" in {
    if (backendNames.contains("firrtl")) {
      iotesters.Driver.execute(
        Array("--target-dir", "test_run_dir/" + dir + "_firrtl_test", "--top-name", dir,

  "running with --is-verbose" should "show more about what's going on in your tester" in {
    iotesters.Driver.execute(
      Array("--target-dir", "test_run_dir/" + dir + "_verbose_test", "--top-name", dir,
        "--is-verbose"), () => new ShiftRows) {
      c => new ShiftRowsUnitTester(c)
    } should be(true)
  }

}

          "--backend-name", "firrtl", "--generate-vcd-output", "on"), () => new ShiftRows) {
        c => new ShiftRowsUnitTester(c)
      } should be(true)


package aes

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

class UnrolledAESUnitTester(c: UnrolledAES, Nk: Int, unrolled: Int, SubBytes_SCD: Boolean, InvSubBytes_SCD: Boolean, expandedKeyMemType: String) extends PeekPokeTester(c) {
  require(Nk == 4 || Nk == 6 || Nk == 8)
  require(expandedKeyMemType == "ROM" || expandedKeyMemType == "Mem" || expandedKeyMemType == "SyncReadMem")

  private val aes_i = c

  val KeyLength: Int = Nk * Params.rows
  val Nr: Int = Nk + 6 // 10, 12, 14 rounds
  val Nrplus1: Int = Nr + 1 // 10+1, 12+1, 14+1

  printf("\nStarting the tests with 4 idle cycles\n")
  poke(aes_i.io.AES_mode, 0) // off
  step(4) // test that things are fine in Idle state

  val input_text = Array(0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34)

  if (expandedKeyMemType == "Mem" || expandedKeyMemType == "SyncReadMem") {
    val roundKey128 = Array(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f)
    val expandedKey128 = Array(
      Array(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f),
      Array(0xd6, 0xaa, 0x74, 0xfd, 0xd2, 0xaf, 0x72, 0xfa, 0xda, 0xa6, 0x78, 0xf1, 0xd6, 0xab, 0x76, 0xfe),
      Array(0xb6, 0x92, 0xcf, 0x0b, 0x64, 0x3d, 0xbd, 0xf1, 0xbe, 0x9b, 0xc5, 0x00, 0x68, 0x30, 0xb3, 0xfe),
      Array(0xb6, 0xff, 0x74, 0x4e, 0xd2, 0xc2, 0xc9, 0xbf, 0x6c, 0x59, 0x0c, 0xbf, 0x04, 0x69, 0xbf, 0x41),
      Array(0x47, 0xf7, 0xf7, 0xbc, 0x95, 0x35, 0x3e, 0x03, 0xf9, 0x6c, 0x32, 0xbc, 0xfd, 0x05, 0x8d, 0xfd),
      Array(0x3c, 0xaa, 0xa3, 0xe8, 0xa9, 0x9f, 0x9d, 0xeb, 0x50, 0xf3, 0xaf, 0x57, 0xad, 0xf6, 0x22, 0xaa),
      Array(0x5e, 0x39, 0x0f, 0x7d, 0xf7, 0xa6, 0x92, 0x96, 0xa7, 0x55, 0x3d, 0xc1, 0x0a, 0xa3, 0x1f, 0x6b),
      Array(0x14, 0xf9, 0x70, 0x1a, 0xe3, 0x5f, 0xe2, 0x8c, 0x44, 0x0a, 0xdf, 0x4d, 0x4e, 0xa9, 0xc0, 0x26),
      Array(0x47, 0x43, 0x87, 0x35, 0xa4, 0x1c, 0x65, 0xb9, 0xe0, 0x16, 0xba, 0xf4, 0xae, 0xbf, 0x7a, 0xd2),
      Array(0x54, 0x99, 0x32, 0xd1, 0xf0, 0x85, 0x57, 0x68, 0x10, 0x93, 0xed, 0x9c, 0xbe, 0x2c, 0x97, 0x4e),
      Array(0x13, 0x11, 0x1d, 0x7f, 0xe3, 0x94, 0x4a, 0x17, 0xf3, 0x07, 0xa7, 0x8b, 0x4d, 0x2b, 0x30, 0xc5))

    val roundKey192 = Array(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17)
    val expandedKey192 = Array(
      Array(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f),
      Array(0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x58, 0x46, 0xf2, 0xf9, 0x5c, 0x43, 0xf4, 0xfe),
      Array(0x54, 0x4a, 0xfe, 0xf5, 0x58, 0x47, 0xf0, 0xfa, 0x48, 0x56, 0xe2, 0xe9, 0x5c, 0x43, 0xf4, 0xfe),
      Array(0x40, 0xf9, 0x49, 0xb3, 0x1c, 0xba, 0xbd, 0x4d, 0x48, 0xf0, 0x43, 0xb8, 0x10, 0xb7, 0xb3, 0x42),
      Array(0x58, 0xe1, 0x51, 0xab, 0x04, 0xa2, 0xa5, 0x55, 0x7e, 0xff, 0xb5, 0x41, 0x62, 0x45, 0x08, 0x0c),
      Array(0x2a, 0xb5, 0x4b, 0xb4, 0x3a, 0x02, 0xf8, 0xf6, 0x62, 0xe3, 0xa9, 0x5d, 0x66, 0x41, 0x0c, 0x08),
      Array(0xf5, 0x01, 0x85, 0x72, 0x97, 0x44, 0x8d, 0x7e, 0xbd, 0xf1, 0xc6, 0xca, 0x87, 0xf3, 0x3e, 0x3c),
      Array(0xe5, 0x10, 0x97, 0x61, 0x83, 0x51, 0x9b, 0x69, 0x34, 0x15, 0x7c, 0x9e, 0xa3, 0x51, 0xf1, 0xe0),
      Array(0x1e, 0xa0, 0x37, 0x2a, 0x99, 0x53, 0x09, 0x16, 0x7c, 0x43, 0x9e, 0x77, 0xff, 0x12, 0x05, 0x1e),
      Array(0xdd, 0x7e, 0x0e, 0x88, 0x7e, 0x2f, 0xff, 0x68, 0x60, 0x8f, 0xc8, 0x42, 0xf9, 0xdc, 0xc1, 0x54),
      Array(0x85, 0x9f, 0x5f, 0x23, 0x7a, 0x8d, 0x5a, 0x3d, 0xc0, 0xc0, 0x29, 0x52, 0xbe, 0xef, 0xd6, 0x3a),
      Array(0xde, 0x60, 0x1e, 0x78, 0x27, 0xbc, 0xdf, 0x2c, 0xa2, 0x23, 0x80, 0x0f, 0xd8, 0xae, 0xda, 0x32),
      Array(0xa4, 0x97, 0x0a, 0x33, 0x1a, 0x78, 0xdc, 0x09, 0xc4, 0x18, 0xc2, 0x71, 0xe3, 0xa4, 0x1d, 0x5d))

    val roundKey256 = Array(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f)
    val expandedKey256 = Array(
      Array(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f),
      Array(0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f),
      Array(0xa5, 0x73, 0xc2, 0x9f, 0xa1, 0x76, 0xc4, 0x98, 0xa9, 0x7f, 0xce, 0x93, 0xa5, 0x72, 0xc0, 0x9c),
      Array(0x16, 0x51, 0xa8, 0xcd, 0x02, 0x44, 0xbe, 0xda, 0x1a, 0x5d, 0xa4, 0xc1, 0x06, 0x40, 0xba, 0xde),
      Array(0xae, 0x87, 0xdf, 0xf0, 0x0f, 0xf1, 0x1b, 0x68, 0xa6, 0x8e, 0xd5, 0xfb, 0x03, 0xfc, 0x15, 0x67),
      Array(0x6d, 0xe1, 0xf1, 0x48, 0x6f, 0xa5, 0x4f, 0x92, 0x75, 0xf8, 0xeb, 0x53, 0x73, 0xb8, 0x51, 0x8d),
      Array(0xc6, 0x56, 0x82, 0x7f, 0xc9, 0xa7, 0x99, 0x17, 0x6f, 0x29, 0x4c, 0xec, 0x6c, 0xd5, 0x59, 0x8b),

      Array(0x7c, 0xcf, 0xf7, 0x1c, 0xbe, 0xb4, 0xfe, 0x54, 0x13, 0xe6, 0xbb, 0xf0, 0xd2, 0x61, 0xa7, 0xdf),
      Array(0xf0, 0x1a, 0xfa, 0xfe, 0xe7, 0xa8, 0x29, 0x79, 0xd7, 0xa5, 0x64, 0x4a, 0xb3, 0xaf, 0xe6, 0x40),
      Array(0x25, 0x41, 0xfe, 0x71, 0x9b, 0xf5, 0x00, 0x25, 0x88, 0x13, 0xbb, 0xd5, 0x5a, 0x72, 0x1c, 0x0a),
      Array(0x4e, 0x5a, 0x66, 0x99, 0xa9, 0xf2, 0x4f, 0xe0, 0x7e, 0x57, 0x2b, 0xaa, 0xcd, 0xf8, 0xcd, 0xea),
      Array(0x24, 0xfc, 0x79, 0xcc, 0xbf, 0x09, 0x79, 0xe9, 0x37, 0x1a, 0xc2, 0x3c, 0x6d, 0x68, 0xde, 0x36))

    val roundKey = Nk match {
      case 4 => roundKey128
      case 6 => roundKey192
      case 8 => roundKey256
    }

    val expandedKey = Nk match {
      case 4 => expandedKey128
      case 6 => expandedKey192
      case 8 => expandedKey256
    }

    printf("\nSending expanded AES key\n")
    // send expanded key to AES memory block
    poke(aes_i.io.AES_mode, 1) // configure key
    for (i <- 0 until Nrplus1) {
      for (j <- 0 until Params.StateLength) {
        poke(aes_i.io.input_text(j), expandedKey(i)(j))
      }
      step(1)
    }

    printf("\nStaying idle for 4 cycles\n")
    poke(aes_i.io.AES_mode, 0) // must stop when all roundKeys were sent
    step(4)
  }

  printf("\nStarting AES cipher mode, sending plaintext\n")
  poke(aes_i.io.AES_mode, 2) // cipher
  // send the plaintext
  for (i <- 0 until Params.StateLength) {
    poke(aes_i.io.input_text(i), input_text(i))
  }
  step(1)
  poke(aes_i.io.AES_mode, 0) // idle

  // remaining rounds
  for (i <- 1 until Nrplus1) {
    step(1)
  }

  val state_e128 = Array(0x89, 0xed, 0x5e, 0x6a, 0x05, 0xca, 0x76, 0x33, 0x81, 0x35, 0x08, 0x5f, 0xe2, 0x1c, 0x40, 0xbd)
  val state_e192 = Array(0xbc, 0x3a, 0xaa, 0xb5, 0xd9, 0x7b, 0xaa, 0x7b, 0x32, 0x5d, 0x7b, 0x8f, 0x69, 0xcd, 0x7c, 0xa8)
  val state_e256 = Array(0x9a, 0x19, 0x88, 0x30, 0xff, 0x9a, 0x4e, 0x39, 0xec, 0x15, 0x01, 0x54, 0x7d, 0x4a, 0x6b, 0x1b)

  val state_e = Nk match {
    case 4 => state_e128
    case 6 => state_e192
    case 8 => state_e256
  }

  printf("\nInspecting cipher output\n")
  // verify aes cipher output
  for (i <- 0 until Params.StateLength)
    expect(aes_i.io.output_text(i), state_e(i))
  expect(aes_i.io.output_valid, 1)

  // store cipher output
  val cipher_output = peek(aes_i.io.output_text)

  printf("\nStaying idle for 4 cycles\n")
  poke(aes_i.io.AES_mode, 0) // off
  step(4)

  printf("\nStarting AES inverse cipher mode, sending ciphertext\n")
  poke(aes_i.io.AES_mode, 3) // inverse cipher
  // send the ciphertext
  for (i <- 0 until Params.StateLength) {
    poke(aes_i.io.input_text(i), cipher_output(i)) // same as state_e(i)
  }
  step(1)
  poke(aes_i.io.AES_mode, 0) // idle

  // remaining rounds
  for (i <- 1 until Nrplus1) {
    step(1)
  }

  printf("\nInspecting inverse cipher output\n")
  //  verify aes inverse cipher output
  for (i <- 0 until Params.StateLength)
    expect(aes_i.io.output_text(i), input_text(i))
  expect(aes_i.io.output_valid, 1)

  printf("\nStaying idle for 4 cycles\n")
  step(4)
}

// Run test with:
// sbt 'testOnly aes.UnrolledAESTester'
// sbt 'testOnly aes.UnrolledAESTester -- -z "using verilator"'
// sbt 'testOnly aes.UnrolledAESTester -- -z "using firrtl"'
// sbt 'testOnly aes.UnrolledAESTester -- -z verbose'
// sbt 'testOnly aes.UnrolledAESTester -- -z vcd'

class UnrolledAESTester extends ChiselFlatSpec {

  private val expandedKeyMemType = "SyncReadMem" // ROM or Mem or SyncReadMem works
  private val SubBytes_SCD = false
  private val InvSubBytes_SCD = false
  private val Nk = 8 // 4, 6, 8 [32-bit words] columns in cipher key
  private val unrolled = 10
  private val backendNames = Array("firrtl", "verilator")
  private val dir = "UnrolledAES"

  for (backendName <- backendNames) {
    "UnrolledAES" should s"execute cipher and inverse cipher (with $backendName)" in {
      Driver(() => new UnrolledAES(Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType), backendName) {
        c => new UnrolledAESUnitTester(c, Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType)
      } should be(true)
    }
  }

  "Basic test using Driver.execute" should "be used as an alternative way to run specification" in {
    iotesters.Driver.execute(
      Array("--target-dir", "test_run_dir/" + dir + "_basic_test", "--top-name", dir),
      () => new UnrolledAES(Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType)) {
      c => new UnrolledAESUnitTester(c, Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType)
    } should be(true)
  }

  "using --backend-name verilator" should "be an alternative way to run using verilator" in {
    if (backendNames.contains("verilator")) {
      iotesters.Driver.execute(
        Array("--target-dir", "test_run_dir/" + dir + "_verilator_test", "--top-name", dir,
          "--backend-name", "verilator"), () => new UnrolledAES(Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType)) {
        c => new UnrolledAESUnitTester(c, Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType)
      } should be(true)
    }
  }

  "using --backend-name firrtl" should "be an alternative way to run using firrtl" in {
    if (backendNames.contains("firrtl")) {
      iotesters.Driver.execute(
        Array("--target-dir", "test_run_dir/" + dir + "_firrtl_test", "--top-name", dir,
          "--backend-name", "firrtl", "--generate-vcd-output", "on"), () => new UnrolledAES(Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType)) {
        c => new UnrolledAESUnitTester(c, Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType)
      } should be(true)
    }
  }

  "running with --is-verbose" should "show more about what's going on in your tester" in {
    iotesters.Driver.execute(
      Array("--target-dir", "test_run_dir/" + dir + "_verbose_test", "--top-name", dir,
        "--is-verbose"), () => new UnrolledAES(Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType)) {
      c => new UnrolledAESUnitTester(c, Nk, unrolled, SubBytes_SCD, InvSubBytes_SCD, expandedKeyMemType)
    } should be(true)
  }

}


// See README.md for license details.

package gcd

import chisel3._

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

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

object GCDRepl extends App {
  iotesters.Driver.executeFirrtlRepl(args, () => new GCD)
}


package lfsr

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

class LFSRUnitTester(c: LFSR) extends PeekPokeTester(c) {
  //init D0123456 to 1
  var D0123456 = 1
  for (t <- 0 until 63) {
    step(1)
    val bit = ((D0123456 >> 5) ^ (D0123456 >> 4)) & 1;
    D0123456 = (D0123456 << 1) | bit;
    if (D0123456 > 63) {
      D0123456 = D0123456 - 64
    }
    expect(c.io.lfsr_6, D0123456)
  }
}

// Run with:
//    sbt 'testOnly lfsr.LFSRTester'
// or sbt 'testOnly lfsr.LFSRTester -- -z verbose'
// or sbt 'testOnly lfsr.LFSRTester -- -z vcd'

class LFSRTester extends ChiselFlatSpec {

  private val backendNames = Array[String]("firrtl", "verilator")
  private val dir = "LFSR"

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

  "Basic test using Driver.execute" should "be used as an alternative way to run specification" in {
    iotesters.Driver.execute(
      Array("--target-dir", "test_run_dir/" + dir + "_basic_test", "--top-name", dir), () => new LFSR) {
      c => new LFSRUnitTester(c)
    } should be(true)
  }

  "using --backend-name verilator" should "be an alternative way to run using verilator" in {
    if (backendNames.contains("verilator")) {
      iotesters.Driver.execute(
        Array("--target-dir", "test_run_dir/" + dir + "_verilator_test", "--top-name", dir,
          "--backend-name", "verilator"), () => new LFSR) {
        c => new LFSRUnitTester(c)
      } should be(true)
    }
  }

  "using --backend-name firrtl" should "be an alternative way to run using firrtl" in {
    if (backendNames.contains("firrtl")) {

    }
  }

  "running with --is-verbose" should "show more about what's going on in your tester" in {
    iotesters.Driver.execute(
      Array("--target-dir", "test_run_dir/" + dir + "_verbose_test", "--top-name", dir,
        "--is-verbose"), () => new LFSR) {
      c => new LFSRUnitTester(c)
    } should be(true)
  }

}

      iotesters.Driver.execute(
        Array("--target-dir", "test_run_dir/" + dir + "_firrtl_test", "--top-name", dir,
          "--backend-name", "firrtl", "--generate-vcd-output", "on"), () => new LFSR) {