package spinal.lib.com.usb.phy

import spinal.core._
import spinal.lib._
import spinal.lib.com.usb.UsbTimer
import spinal.lib.com.usb.udc.UsbDeviceCtrl.PhyIo
import spinal.lib.fsm.{State, StateMachine}

case class UsbDevicePhyNative(sim : Boolean = false) extends Component{
  val io = new Bundle {
    val ctrl = slave(PhyIo())
    val usb = master(UsbLsFsPhyAbstractIo())
    val power = in Bool()
    val pullup = out Bool()
  }

  val fsRatioExact = (ClockDomain.current.frequency.getValue.toDouble/12e6)
  val fsRatio = fsRatioExact.round.toInt

  io.pullup := io.ctrl.pullup

  val timer = new UsbTimer(counterTimeMax = 0.084e-6 * 8, fsRatio) {
    val oneCycle = cycles(1)
    val twoCycle = cycles(2)
    val fourCycle = cycles(5)

    lowSpeed := False
  }

  val rxToTxDelay = new UsbTimer(0.084e-6*2, fsRatio){
    val twoCycle = cycles(2)
    val active = RegInit(False) clearWhen(twoCycle)
    lowSpeed := False
  }

  val tx = new Area {
    val encoder = new Area {
      val input = new Area {
        val valid = False
        val ready = False
        val data = Bool().assignDontCare()
      }
      val output = new Area {
        val valid = False
        val se0 = False
        val data = Bool().assignDontCare()
      }

      val counter = Reg(UInt(3 bits))
      val state = Reg(Bool())

      when(input.valid) {
        output.valid := input.valid

        when(input.data) {
          output.data := state
          when(timer.oneCycle) {
            counter := counter + 1;
            input.ready := True
            when(counter === 5){
              timer.clear := True
              input.ready := False
              state := !state
            }
            when(counter === 6){
              counter := 0;
            }
          }
        } otherwise {
          output.data := !state
          when(timer.oneCycle) {
            counter := 0;
            input.ready := True
            state := !state
          }
        }
      }

      when(!input.valid) {
        counter := 0
        state := True
      }

      when(input.ready) {
        timer.clear := True
      }
    }


    val serialiser = new Area {
      val input = Stream(Bits(8 bits))
      val bitCounter = Reg(UInt(3 bits))

      input.valid := False
      input.payload.assignDontCare()
      input.ready := False

      when(input.valid) {
        encoder.input.valid := True
        encoder.input.data := input.payload(bitCounter)
        when(encoder.input.ready) {
          bitCounter := bitCounter + 1
          when(bitCounter === 7) {
            input.ready := True
          }
        }
      }

      when(!input.valid || input.ready) {
        bitCounter := 0
      }
    }

    val frame = new StateMachine {
      val IDLE, TAKE_LINE, SYNC, DATA, EOP_0, EOP_1, EOP_2 = new State
      setEntry(IDLE)

      val busy = this.isStarted



      def doByte(current : State, value : Bits, next : State): Unit = current.whenIsActive{
        serialiser.input.valid := True
        serialiser.input.payload := value
        when(serialiser.input.ready) {
          goto(next)
        }
      }

      IDLE whenIsActive {
        timer.clear := True
        when(io.ctrl.tx.stream.valid && !rxToTxDelay.active) {
          goto(TAKE_LINE)
        }
      }

      //Avoid special signal reflection of the first bit of the frame
      TAKE_LINE whenIsActive{
        encoder.output.valid    := True
        encoder.output.data     := True
        timer.lowSpeed := False
        when(timer.oneCycle) {
          timer.clear := True
          goto(SYNC)
        }
      }

      doByte(SYNC, 0x80, DATA)

      io.ctrl.tx.stream.ready := False
      DATA whenIsActive {
        serialiser.input.valid := True
        serialiser.input.payload := io.ctrl.tx.stream.fragment
        when(serialiser.input.ready) {
          io.ctrl.tx.stream.ready := True
          when(io.ctrl.tx.stream.last) {
            goto(EOP_0)
          }
        }
      }

      io.ctrl.tx.eop := False
      EOP_0 whenIsActive {
        encoder.output.valid := True
        encoder.output.se0 := True
        when(timer.twoCycle) {
          timer.clear := True
          io.ctrl.tx.eop := True
          goto(EOP_1)
        }
      }
      EOP_1 whenIsActive {
        encoder.output.valid := True
        encoder.output.data := True
        when(timer.oneCycle) {
          timer.clear := True
          goto(EOP_2)
        }
      }
      EOP_2 whenIsActive {
        when(timer.twoCycle) {
          timer.clear := True
          goto(IDLE)
        }
      }
    }
  }

  val rx = new Area{
    val filter = UsbLsFsPhyFilter(fsRatio)
    filter.io.lowSpeed := False
    filter.io.usb.dp := io.usb.rx.dp
    filter.io.usb.dm := io.usb.rx.dm

    val j =  filter.io.filtred.dp && !filter.io.filtred.dm
    val k = !filter.io.filtred.dp &&  filter.io.filtred.dm

    val stuffingError = Reg(Bool())

    val waitSync = False
    val decoder = new Area{
      val state = Reg(Bool())

      val output = Flow(Bool())
      output.valid := False
      output.payload.assignDontCare()

      when(filter.io.filtred.sample){
        output.valid := True
        when(state ^ filter.io.filtred.d){
          output.payload := False
          state := !state
        } otherwise {
          output.payload := True
        }
      }

      when(waitSync){
        state := False
      }
    }

    val destuffer = new Area{
      val counter = Reg(UInt(3 bits))
      val unstuffNext = counter === 6

      val output = decoder.output.throwWhen(unstuffNext)

      when(decoder.output.valid){
        counter := counter + 1
        when(!decoder.output.payload || unstuffNext){
          counter := 0
          stuffingError setWhen(decoder.output.payload)
        }
      }

      when(waitSync){
        counter := 0
      }
    }

    val history = new Area{
      val updated = CombInit(destuffer.output.valid)
      val value = History(destuffer.output.payload, 0 to 7, when = updated).reverse.asBits
      val sync = new Area {
        val pattern = 0x2A ^ 0xFF
        val hit = updated && value === pattern
      }
    }

    val eop = new Area{
      val maxThreshold = fsRatio*3
      val minThreshold = fsRatio*2*2/3
      val counter = Reg(UInt(log2Up(fsRatio*8*3+1) bits)) init(0)
      val maxHit = counter === maxThreshold
      val hit = False

      when(!filter.io.filtred.dp && !filter.io.filtred.dm){
        when(!maxHit) {
          counter := counter + 1
        }
      } otherwise {
        counter := 0
      }
      when(j){
        when(counter >= minThreshold && !maxHit){
          hit := True
        }
      }
    }

    val packet = new StateMachine {
      val IDLE, PACKET, ERRORED = new State
      setEntry(IDLE)

      val counter = Reg(UInt(3 bits))
      IDLE.onEntry(waitSync := True)
      IDLE.whenIsActive{
        waitSync := True
        counter := 0
        stuffingError := False
        when(history.sync.hit){
          goto(PACKET)
        }
      }

      io.ctrl.rx.active := False
      io.ctrl.rx.stuffingError := False
      io.ctrl.rx.flow.valid := False
      io.ctrl.rx.flow.payload := history.value

      PACKET whenIsActive {
        io.ctrl.rx.active := True
        io.ctrl.rx.stuffingError := stuffingError
        when(destuffer.output.valid){
          counter := counter + 1
          when(counter === 7){
            io.ctrl.rx.flow.valid := True
            when(stuffingError){
              goto(ERRORED)
            }
          }
        }
      }

      //Ensure that all activities on the bus are done before released the FSM
      val errorTimeout = new UsbTimer(20.0*0.084e-6,fsRatio){
        lowSpeed := False

        val trigger = cycles(20)
        val p,n = Reg(Bool())

        ERRORED.onEntry{
          clear := True
        }
        ERRORED whenIsActive{
          io.ctrl.rx.active := True
          p := filter.io.filtred.dp
          n := filter.io.filtred.dm
          when(p =/= filter.io.filtred.dp || n =/= filter.io.filtred.dm){
            clear := True
          }
          when(trigger){
            goto(IDLE)
          }
        }
      }


      always{
        when(eop.hit){
          rxToTxDelay.clear := True
          rxToTxDelay.active := True
          goto(IDLE)
        }
        when(tx.encoder.output.valid){
          goto(IDLE)
        }
      }
    }


    val timerLong = new UsbTimer(counterTimeMax = 21e-3*2, fsRatio) {
      val factor = if(sim) 0.005 else 1.0
      val resume = trigger(19.9e-3*factor)
      val reset = trigger(9.9e-3*factor)
      val suspend = trigger(2.9e-3*factor)
      val oneBit = trigger(83e-9)
      val threeBit = trigger(83e-9*3)
      val hadOne = RegInit(False) setWhen(oneBit) clearWhen(clear)
      val hadTree = RegInit(False) setWhen(threeBit) clearWhen(clear)
      lowSpeed := False
    }

    val detect = new Area{
      val current = filter.io.filtred.dm ## filter.io.filtred.dp
      val previous = RegNext(current) init(3)
      when(timerLong.counter.msb){
        timerLong.inc := False
      }
      when(current =/= previous){
        timerLong.clear := True
      }

      val isReset     = current === 0
      val isSuspend   = current === 1
      val resumeState = RegInit(False) clearWhen(current =/= 0 && current =/= previous) setWhen(timerLong.resume && current === 2)
      val isResume = resumeState && timerLong.hadOne && !timerLong.hadTree && current === 1

      val resetState = RegInit(False) setWhen(timerLong.reset && isReset) clearWhen(!isReset)
      val suspendState = RegInit(False) setWhen(timerLong.suspend && isSuspend) clearWhen(!isSuspend)
      io.ctrl.reset := resetState
      io.ctrl.suspend := suspendState
      io.ctrl.disconnect := False
      io.ctrl.resume.valid := RegNext(isResume) init(False)
    }
  }

  val tickTimer = new Area {
    val counter = CounterFreeRun(fsRatio)
    val tick = counter.willOverflow === True
    io.ctrl.tick := tick
  }

  io.usb.tx.enable := tx.encoder.output.valid
  io.usb.tx.se0 := tx.encoder.output.se0
  io.usb.tx.data := tx.encoder.output.data

  io.ctrl.power := io.power
}

