#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from hwt.code import If, Switch, Concat
from hwt.code_utils import rename_signal
from hwt.hdl.types.bits import Bits
from hwt.hdl.types.enum import HEnum
from hwt.interfaces.utils import propagateClkRst, addClkRst
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwt.synthesizer.unit import Unit
from hwtLib.peripheral.usb.usb2.ulpi import Ulpi, ULPI_TX_CMD, ULPI_REG, \
ulpi_reg_function_control_t, ulpi_reg_function_control_t_reset_default, \
ulpi_reg_otg_control_t, ulpi_reg_otg_control_t_reset_defaults, \
ulpi_reg_usb_interrupt_status_t_reset_default
from hwtLib.peripheral.usb.usb2.utmi import Utmi_8b, utmi_interrupt_t
from pyMathBitPrecise.bit_utils import mask
[docs]class Utmi_to_Ulpi(Unit):
"""
The ULPI is an interface which reduces the number of signals for UTMI+ interface.
This reduction is done using a register file which drives signals which are not used
and bi-directional wiring. This component does translation of ULPI to UTMI+ by keeping copy of UTMI+
registers and synchronizing the changes and it also handles the drive of the bi-directional wires.
:note: For up to UTMI+ Level 3
Based on https://raw.githubusercontent.com/ultraembedded/core_ulpi_wrapper/3c202963ac4b4ae50cadb44ce79c11463d3c6484/src_v/ulpi_wrapper.v
.. hwt-autodoc::
"""
def _declr(self):
addClkRst(self)
# PHY is a master for UTMI/ULPI style interface
self.utmi = Utmi_8b()._m()
self.ulpi = Ulpi()
[docs] def ulpi_turnaround_detect(self, ulpi_dir: RtlSignal):
#-----------------------------------------------------------------
# Bus turnaround detect
#-----------------------------------------------------------------
ulpi_dir_q = self._reg("ulpi_dir_q", def_val=1)
ulpi_dir_q(ulpi_dir)
turnaround_w = rename_signal(self, ulpi_dir_q != ulpi_dir._eq(Ulpi.DIR.PHY), "turnaround_w")
return turnaround_w
[docs] @staticmethod
def parse_RX_CMD(ulpi_data, utmi_linestate_q, utmi_interrupt_q, utmi_rxactive_q, utmi_rxerror_q):
return [
utmi_linestate_q(ulpi_data[2:0]),
Switch(ulpi_data[4:2])\
.Case(0b00,
utmi_interrupt_q.SessEnd(1),
utmi_interrupt_q.SessValid(0),
utmi_interrupt_q.VbusValid(0),
).Case(0b01,
utmi_interrupt_q.SessEnd(0),
utmi_interrupt_q.SessValid(0),
utmi_interrupt_q.VbusValid(0),
).Case(0b10,
utmi_interrupt_q.SessValid(1),
utmi_interrupt_q.VbusValid(0),
).Case(0b11,
utmi_interrupt_q.VbusValid(1),
),
Switch(ulpi_data[6:4])\
.Case(0b00,
utmi_rxactive_q(0),
utmi_rxerror_q(0)
).Case(0b01,
utmi_rxactive_q(1),
utmi_rxerror_q(0)
).Case(0b10,
utmi_interrupt_q.HostDisconnect(1)
).Case(0b11,
utmi_rxactive_q(1),
utmi_rxerror_q(1)
),
utmi_interrupt_q.IdGnd(ulpi_data[6])
]
def _impl(self):
ulpi: Ulpi = self.ulpi
utmi: Utmi_8b = self.utmi
# Description:
# - Converts from UTMI interface to reduced pin count ULPI.
# - No support for low power mode.
# - I/O synchronous to 60MHz ULPI clock input (from PHY)
# - Tested against SMSC/Microchip USB3300 in device mode.
#-----------------------------------------------------------------
# States
#-----------------------------------------------------------------
state_t = HEnum("state_t", ["w", "idle", "cmd", "data", "reg"])
state_q = self._reg("state_q", dtype=state_t, def_val=state_t.idle)
#-----------------------------------------------------------------
# UTMI Mode Select
#-----------------------------------------------------------------
# flag which tells that the function mode register is now ready writen to PHY
mode_update_q = self._reg("mode_update_q", def_val=0)
function_control_q = self._reg("function_control_q", ulpi_reg_function_control_t,
def_val=ulpi_reg_function_control_t_reset_default)
mode_write_q = self._reg("mode_write_q", def_val=0)
# Detect register write completion
mode_complete_w = rename_signal(self, ((state_q._eq(state_t.reg) & mode_write_q) & ulpi.nxt) & ulpi.dir._eq(Ulpi.DIR.LINK), "mode_complete_w")
#-----------------------------------------------------------------
# UTMI OTG Control
#-----------------------------------------------------------------
otg_update_q = self._reg("otg_update_q", def_val=0)
otg_control_q = self._reg("otg_control_q", ulpi_reg_otg_control_t,
def_val=ulpi_reg_otg_control_t_reset_defaults)
otg_write_q = self._reg("otg_write_q", def_val=0)
# Detect register write completion
otg_complete_w = rename_signal(self, ((state_q._eq(state_t.reg) & otg_write_q) & ulpi.nxt) & ulpi.dir._eq(Ulpi.DIR.LINK), "otg_complete_w")
#-----------------------------------------------------------------
# Tx Buffer - decouple UTMI Tx from PHY I/O
#-----------------------------------------------------------------
# tx_fifo = HandshakedFifo(Handshaked)
# tx_fifo.DATA_WIDTH = 8
# tx_fifo.DEPTH = 2
# self.tx_fifo = tx_fifo
#-----------------------------------------------------------------
# Implementation
#-----------------------------------------------------------------
# Xilinx placement pragmas:
# synthesis attribute IOB of ulpi_data_q is "TRUE"
# synthesis attribute IOB of ulpi_stp_q is "TRUE"
ulpi_data_q = self._reg("ulpi_data_q", Bits(8), def_val=0)
ulpi_stp_q = self._reg("ulpi_stp_q", def_val=0)
data_q = self._reg("data_q", Bits(8), def_val=0)
utmi_rxvalid_q = self._reg("utmi_rxvalid_q", def_val=0)
utmi_rxerror_q = self._reg("utmi_rxerror_q", def_val=0)
utmi_rxactive_q = self._reg("utmi_rxactive_q", def_val=0)
utmi_linestate_q = self._reg("utmi_linestate_q", Bits(2), def_val=0)
utmi_data_q = self._reg("utmi_data_q", Bits(8), def_val=0)
# interupts are cleared once new RX CMD is recieved and it does not contain the event flag
utmi_interrupt_q = self._reg("utmi_interrupt_q", utmi_interrupt_t,
def_val=ulpi_reg_usb_interrupt_status_t_reset_default)
utmi.interrupt(utmi_interrupt_q)
# Not interrupted by a Rx
function_control_q(utmi.function_control, exclude=[function_control_q.Reset])
If(mode_update_q & mode_complete_w,
function_control_q.Reset(0)
).Else(
function_control_q.Reset(utmi.function_control.Reset)
)
If(mode_update_q & mode_complete_w,
mode_update_q(0),
).Elif((function_control_q != utmi.function_control) | utmi.function_control.Reset,
mode_update_q(1)
)
# Not interrupted by a Rx
otg_control_q(utmi.otg_control)
If(otg_update_q & otg_complete_w,
otg_update_q(0)
).Elif(otg_control_q != utmi.otg_control,
otg_update_q(1)
)
turnaround_w = self.ulpi_turnaround_detect(ulpi.dir)
# utmi_tx_to_ulpi_vld = tx_fifo.dataOut.vld
# Push
# tx_fifo.dataIn.vld(utmi.tx.vld & utmi.tx.rd)
# tx_fifo.dataIn.data(utmi.tx.data)
# Pop
# tx_fifo.dataOut.rd(utmi_tx_to_ulpi_vld & utmi_tx_accept_w)
# utmi.tx.rd(tx_fifo.dataIn.rd & tx_delay_complete_w)
utmi_tx_to_ulpi_vld = utmi.tx.vld
utmi_tx_data_w = utmi.tx.data
utmi_tx_accept_w = rename_signal(
self,
~turnaround_w & ulpi.dir._eq(Ulpi.DIR.LINK) & (
(state_q._eq(state_t.idle) & ~(mode_update_q | otg_update_q | turnaround_w)) |
(state_q._eq(state_t.data) & ulpi.nxt)
),
"utmi_tx_accept_w")
utmi.tx.rd(utmi_tx_accept_w)
ulpi_stp_q(~turnaround_w & ulpi.dir._eq(Ulpi.DIR.LINK) & ulpi.nxt &
(state_q._eq(state_t.reg) |
(state_q._eq(state_t.data) & ~utmi_tx_to_ulpi_vld)
)
)
utmi_rxvalid_q(~turnaround_w & ulpi.dir._eq(Ulpi.DIR.PHY) & ulpi.nxt)
If(turnaround_w,
If(ulpi.dir._eq(Ulpi.DIR.PHY) & ulpi.nxt,
# Turnaround: Input + NXT - set RX_ACTIVE
utmi_rxactive_q(1),
# Register write - abort
If(state_q._eq(state_t.reg),
state_q(state_t.idle),
ulpi_data_q(0),
)
).Elif(ulpi.dir._eq(Ulpi.DIR.LINK),
utmi_rxactive_q(0),
# Register write - abort
If(state_q._eq(state_t.reg),
state_q(state_t.idle),
ulpi_data_q(0),
)
)
).Else(
If(ulpi.dir._eq(Ulpi.DIR.PHY),
If(ulpi.nxt,
#-----------------------------------------------------------------
# Input: RX_DATA
#-----------------------------------------------------------------
utmi_rxactive_q(1),
utmi_data_q(ulpi.data.i)
).Else(
#-----------------------------------------------------------------
# Input: RX_CMD (phy status), decode encoded status/event bits from this byte
#-----------------------------------------------------------------
self.parse_RX_CMD(ulpi.data.i,
utmi_linestate_q, utmi_interrupt_q,
utmi_rxactive_q, utmi_rxerror_q)
)
).Else(
#-----------------------------------------------------------------
# Output
#-----------------------------------------------------------------
If(state_q._eq(state_t.idle),
If(mode_update_q,
# IDLE: Pending mode update
state_q(state_t.cmd),
ulpi_data_q(ULPI_TX_CMD.REGW(ULPI_REG.Function_Control)),
data_q(function_control_q._reinterpret_cast(data_q._dtype) & mask(7)),
otg_write_q(0),
mode_write_q(1),
).Elif(otg_update_q,
# IDLE: Pending OTG control update
state_q(state_t.cmd),
ulpi_data_q(ULPI_TX_CMD.REGW(ULPI_REG.OTG_Control)),
data_q(otg_control_q._reinterpret_cast(data_q._dtype)),
otg_write_q(1),
mode_write_q(0),
).Elif(utmi_tx_to_ulpi_vld,
# IDLE: Pending transmit
# data should have USB_PID header and this is just to be sure
ulpi_data_q(ULPI_TX_CMD.USB_PID(utmi_tx_data_w[4:0])),
state_q(state_t.data)
)
).Elif(ulpi.nxt,
If(state_q._eq(state_t.cmd),
# Command, Write Register
state_q(state_t.reg),
ulpi_data_q(data_q),
).Elif(state_q._eq(state_t.reg),
# Data (register write)
state_q(state_t.idle),
ulpi_data_q(0),
otg_write_q(0),
mode_write_q(0),
).Elif(state_q._eq(state_t.data),
# Data
If(utmi_tx_to_ulpi_vld,
state_q(state_t.data),
ulpi_data_q(utmi_tx_data_w),
).Else(
# End of packet
state_q(state_t.idle),
ulpi_data_q(0),
)
)
)
)
)
ulpi.data.o(ulpi_data_q)
ulpi.data.t(Concat(*(utmi_tx_accept_w for _ in range(8))))
ulpi.stp(ulpi_stp_q)
utmi.LineState(utmi_linestate_q)
utmi.rx.data(utmi_data_q)
utmi.rx.error(utmi_rxerror_q)
utmi.rx.active(utmi_rxactive_q)
utmi.rx.valid(utmi_rxvalid_q)
propagateClkRst(self)
if __name__ == "__main__":
from hwt.synthesizer.utils import to_rtl_str
u = Utmi_to_Ulpi()
print(to_rtl_str(u))