package spinal.lib.bus.avalon

import spinal.core._
import spinal.lib._
import spinal.lib.bus.misc._
import scala.collection.Seq

object AvalonMMSlaveFactory{
  def getAvalonConfig(addressWidth: Int,
                      dataWidth: Int,
                      useByteEnable: Boolean = false) = {
    AvalonMMConfig.pipelined(
      addressWidth = addressWidth,
      dataWidth = dataWidth,
      useByteEnable = useByteEnable
    ).copy(
      useWaitRequestn = true
    )
  }

  def apply(bus: AvalonMM) = new AvalonMMSlaveFactory(bus)
}


class AvalonMMSlaveFactory(bus: AvalonMM) extends BusSlaveFactoryDelayed{
  assert(bus.config == AvalonMMSlaveFactory.getAvalonConfig(bus.config.addressWidth, bus.config.dataWidth, bus.config.useByteEnable))

  bus.waitRequestn := True

  val readAtCmd = Flow(Bits(bus.config.dataWidth bits))
  val readAtRsp = readAtCmd.stage()

  val askWrite = (bus.write).allowPruning()
  val askRead  = (bus.read).allowPruning()
  val doWrite  = (bus.waitRequestn &&  bus.write).allowPruning()
  val doRead   = (bus.waitRequestn &&  bus.read).allowPruning()

  bus.readDataValid := readAtRsp.valid
  bus.readData := readAtRsp.payload
  if (bus.config.useResponse) {
    when(writeErrorFlag && doWrite) {
      bus.setSLVERR
    }elsewhen (readErrorFlag && doRead) {
      bus.setSLVERR
    }otherwise {
      bus.setOKEY
    }
  }

  readAtCmd.valid := doRead
  readAtCmd.payload := 0

  override def readAddress() : UInt = bus.address
  override def writeAddress() : UInt = bus.address

  override def writeByteEnable(): Bits = bus.byteEnable

  override def readHalt(): Unit = bus.waitRequestn := False
  override def writeHalt(): Unit = bus.waitRequestn := False

  override def build(): Unit = {
    super.doNonStopWrite(bus.writeData)

    def doMappedElements(jobs : Seq[BusSlaveFactoryElement]) = super.doMappedElements(
      jobs = jobs,
      askWrite = askWrite,
      askRead = askRead,
      doWrite = doWrite,
      doRead = doRead,
      writeData = bus.writeData,
      readData = readAtCmd.payload
    )

    switch(bus.address) {
      for ((address, jobs) <- elementsPerAddress if address.isInstanceOf[SingleMapping]) {
        is(address.asInstanceOf[SingleMapping].address) {
          doMappedElements(jobs)
        }
      }
    }

    for ((address, jobs) <- elementsPerAddress if !address.isInstanceOf[SingleMapping]) {
      when(address.hit(bus.address)){
        doMappedElements(jobs)
      }
    }
  }

  override def busDataWidth: Int = bus.config.dataWidth

  override def wordAddressInc: Int = bus.config.addressUnits match {
    case WORDS   => 1
    case SYMBOLS => busDataWidth / 8
  }
}
