package vexriscv.ip.fpu

import spinal.core._
import spinal.lib._


object Fpu{

  object Function{
    val MUL = 0
    val ADD = 1
  }

}


case class FpuFloatDecoded() extends Bundle{
  val isNan = Bool()
  val isNormal = Bool()
  val isSubnormal = Bool()
  val isZero = Bool()
  val isInfinity = Bool()
  val isQuiet = Bool()
}

object FpuFloat{
  val ZERO = 0
  val INFINITY = 1
  val NAN = 2
  val NAN_CANONICAL_BIT = 2
}

case class FpuFloat(exponentSize: Int,
                    mantissaSize: Int) extends Bundle {
  val mantissa = UInt(mantissaSize bits)
  val exponent = UInt(exponentSize bits)
  val sign = Bool()
  val special = Bool()

  def withInvertSign : FpuFloat ={
    val ret = FpuFloat(exponentSize,mantissaSize)
    ret.sign := !sign
    ret.exponent := exponent
    ret.mantissa := mantissa
    ret
  }

  def isNormal    = !special
  def isZero      =  special && exponent(1 downto 0) === FpuFloat.ZERO
  def isInfinity  =  special && exponent(1 downto 0) === FpuFloat.INFINITY
  def isNan       =  special && exponent(1 downto 0) === FpuFloat.NAN
  def isQuiet     =  mantissa.msb
  def isNanSignaling  = special && exponent(1 downto 0) === FpuFloat.NAN && !isQuiet
  def isCanonical = exponent(FpuFloat.NAN_CANONICAL_BIT)

  def setNormal    =  { special := False }
  def setZero      =  { special := True; exponent(1 downto 0) := FpuFloat.ZERO }
  def setInfinity  =  { special := True; exponent(1 downto 0) := FpuFloat.INFINITY }
  def setNan       =  { special := True; exponent(1 downto 0) := FpuFloat.NAN; exponent(FpuFloat.NAN_CANONICAL_BIT) := False}
  def setNanQuiet  =  { special := True; exponent(1 downto 0) := FpuFloat.NAN; exponent(FpuFloat.NAN_CANONICAL_BIT) := True; mantissa.msb := True; }

  def decode() = {
    val ret = FpuFloatDecoded()
    ret.isZero      := isZero
    ret.isNormal    := isNormal
    ret.isInfinity  := isInfinity
    ret.isNan       := isNan
    ret.isQuiet     := mantissa.msb
    ret
  }

  def decodeIeee754() = {
    val ret = FpuFloatDecoded()
    val expZero = exponent === 0
    val expOne = exponent === exponent.maxValue
    val manZero = mantissa === 0
    ret.isZero := expZero && manZero
    ret.isSubnormal := expZero && !manZero
    ret.isNormal := !expOne && !expZero
    ret.isInfinity := expOne && manZero
    ret.isNan := expOne && !manZero
    ret.isQuiet := mantissa.msb
    ret
  }
}

object FpuOpcode extends SpinalEnum{
  val LOAD, STORE, MUL, ADD, FMA, I2F, F2I, CMP, DIV, SQRT, MIN_MAX, SGNJ, FMV_X_W, FMV_W_X, FCLASS, FCVT_X_X = newElement()
}

object FpuFormat extends SpinalEnum{
  val FLOAT, DOUBLE = newElement()
}

object FpuRoundMode extends SpinalEnum(){
  val RNE, RTZ, RDN, RUP, RMM = newElement()
  defaultEncoding = SpinalEnumEncoding("opt")(
    RNE -> 0,
    RTZ -> 1,
    RDN -> 2,
    RUP -> 3,
    RMM -> 4
  )
}
object FpuRoundModeInstr extends SpinalEnum(){
  val RNE, RTZ, RDN, RUP, RMM, DYN = newElement()
  defaultEncoding = SpinalEnumEncoding("opt")(
    RNE -> 0,
    RTZ -> 1,
    RDN -> 2,
    RUP -> 3,
    RMM -> 4,
    DYN -> 7
  )
}


case class FpuParameter( withDouble : Boolean,
                         asyncRegFile : Boolean = false,
                         mulWidthA : Int = 18,
                         mulWidthB : Int = 18,
                         schedulerM2sPipe : Boolean = false,
                         sim : Boolean = false,
                         withAdd : Boolean = true,
                         withMul : Boolean = true,
                         withDivSqrt : Boolean = false,
                         withDiv : Boolean = true,
                         withSqrt : Boolean = true,
                         withShortPipMisc : Boolean = true){

  val internalMantissaSize = if(withDouble) 52 else 23
  val storeLoadType = HardType(Bits(if(withDouble) 64 bits else 32 bits))
  val internalExponentSize = (if(withDouble) 11 else 8) + 1
  val internalFloating = HardType(FpuFloat(exponentSize = internalExponentSize, mantissaSize = internalMantissaSize))
  val writeFloating = HardType(FpuFloat(exponentSize = internalExponentSize, mantissaSize = internalMantissaSize+1))

  val rfAddress = HardType(UInt(5 bits))

  val Opcode = FpuOpcode
  val Format = FpuFormat
  val argWidth = 2
  val Arg = HardType(Bits(2 bits))
}

case class FpuFlags() extends Bundle{
  val NX,  UF,  OF,  DZ,  NV = Bool()
  def any = List(NX,  UF,  OF,  DZ,  NV).orR
}

case class FpuCompletion() extends Bundle{
  val flags = FpuFlags()
  val written = Bool() //Used for verification purposes
}

case class FpuCmd(p : FpuParameter) extends Bundle{
  val opcode = p.Opcode()
  val arg = Bits(2 bits) 
  val rs1, rs2, rs3 = p.rfAddress()
  val rd = p.rfAddress()
  val format = p.Format()
  val roundMode = FpuRoundMode()
}

case class FpuCommit(p : FpuParameter) extends Bundle{
  val opcode = FpuOpcode()
  val rd = UInt(5 bits)
  val write = Bool()
  val value = p.storeLoadType() // IEEE 754
}

case class FpuRsp(p : FpuParameter) extends Bundle{
  val value = p.storeLoadType() // IEEE754 store || Integer
  val NV, NX = Bool()
}

case class FpuPort(p : FpuParameter) extends Bundle with IMasterSlave {
  val cmd = Stream(FpuCmd(p))
  val commit = Stream(FpuCommit(p))
  val rsp = Stream(FpuRsp(p))
  val completion = Flow(FpuCompletion())

  override def asMaster(): Unit = {
    master(cmd, commit)
    slave(rsp)
    in(completion)
  }
}
