package spinal.lib.bus.amba4.axi

import spinal.core._
import spinal.lib._
import spinal.lib.bus.misc._

object Axi4SlaveFactory {
  def apply(bus: Axi4) = new Axi4SlaveFactory(bus)
}

class Axi4SlaveFactory(bus: Axi4) extends BusSlaveFactoryDelayed {

  val readHaltRequest = False
  val writeHaltRequest = False

  var writeCmd = bus.writeCmd.unburstify
  val writeJoinEvent = StreamJoin.arg(writeCmd, bus.writeData)
  val writeRsp = Stream(Axi4B(bus.config))
  bus.writeRsp << writeRsp.stage()
  val writeDataLast = if(bus.writeData.last != null) bus.writeData.last else True
  when(writeDataLast) {
    // backpressure in last beat
    writeJoinEvent.ready := writeRsp.ready && !writeHaltRequest
    writeRsp.valid := writeJoinEvent.fire
  } otherwise {
    // otherwise, stall W channel when writeHaltRequest
    writeJoinEvent.ready := !writeHaltRequest
    writeRsp.valid := False
  }

  val readCmd = bus.ar.unburstify
  var readDataStage = readCmd.stage()
  val readRsp = Axi4R(bus.config)
  bus.readRsp << readDataStage.haltWhen(readHaltRequest).translateWith(readRsp)

  // only one outstanding request is supported
  if(bus.config.useId) writeRsp.id := writeCmd.id
  if (writeRsp.config.useResp) {
    when(writeErrorFlag) {
      writeRsp.setSLVERR()
    } otherwise {
      writeRsp.setOKAY()
    }
  }
  if (readRsp.config.useResp) {
    when(readErrorFlag) {
      readRsp.setSLVERR()
    } otherwise {
      readRsp.setOKAY()
    }
  }
  readRsp.data := 0

  if(readRsp.last != null) {
    readRsp.last := readDataStage.last
  }
  if(bus.config.useId) readRsp.id := readDataStage.id

  val writeOccur = writeJoinEvent.fire
  val readOccur = bus.readRsp.fire

  def maskAddress(addr : UInt) = addr & ~U(bus.config.dataWidth/8 -1, bus.config.addressWidth bits)
  val readAddressMasked = maskAddress(readDataStage.addr)
  val writeAddressMasked = maskAddress(writeCmd.addr)

  override def readAddress(): UInt  = readAddressMasked
  override def writeAddress(): UInt = writeAddressMasked

  override def writeByteEnable(): Bits = bus.writeData.strb

  override def readHalt(): Unit = readHaltRequest := True
  override def writeHalt(): Unit = writeHaltRequest := True

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

    switch(writeAddress()) {
      for ((address, jobs) <- elementsPerAddress ) address match {
        case address : SingleMapping =>
          assert(address.address % (bus.config.dataWidth/8) == 0)
          is(address.address) {
            doMappedWriteElements(jobs, writeJoinEvent.valid, writeOccur, bus.writeData.data)
          }
        case _ =>
      }
    }

    for ((address, jobs) <- elementsPerAddress if !address.isInstanceOf[SingleMapping]) {
      when(address.hit(writeAddress())) {
        doMappedWriteElements(jobs, writeJoinEvent.valid, writeOccur, bus.writeData.data)
      }
    }


    switch(readAddress()) {
      for ((address, jobs) <- elementsPerAddress) address match {
        case address : SingleMapping =>
          assert(address.address % (bus.config.dataWidth/8) == 0)
          is(address.address) {
            doMappedReadElements(jobs, readDataStage.valid, readOccur, readRsp.data)
          }
        case _ =>
      }
    }

    for ((address, jobs) <- elementsPerAddress if !address.isInstanceOf[SingleMapping]) {
      when(address.hit(readAddress())) {
        doMappedReadElements(jobs, readDataStage.valid, readOccur, readRsp.data)
      }
    }
  }

  override def wordAddressInc: Int = busDataWidth / 8

  override def busDataWidth: Int = bus.config.dataWidth
}
