# SPDX-License-Identifier: BSD-3-Clause
#
# This file is part of Torii-USB.
#
# Copyright (c) 2020 Great Scott Gadgets <info@greatscottgadgets.com>

''' Packet transmission handling gateware. '''

from torii.hdl                      import Array, Cat, Elaboratable, Module, Signal

from usb_construct.types.superspeed import HeaderPacketType, LinkCommand

from ...stream                      import SuperSpeedStreamInterface, USBRawSuperSpeedStream
from ..physical.coding              import EDB, END, EPF, SDP, SHP, get_word_for_symbols
from .command                       import LinkCommandDetector
from .crc                           import DataPacketPayloadCRC, HeaderPacketCRC, compute_usb_crc5
from .header                        import HeaderPacket, HeaderQueue

class RawPacketTransmitter(Elaboratable):
	''' Class that generates and sends header packets; with an optional payload attached.

	This class generates the checks required at the link layer of the USB specification;
	which include checking the CRC-5 and CRC-16 embedded within the header packet, and
	the CRC-32 embedded within any associated payloads.

	Attributes
	----------
	source: USBRawSuperSpeedStream(), output stream
		Stream that carries the generated packets.

	header: HeaderPacket(), input
		The header packet to be sent. The CRC fields do not need to be valid, and will
		be ignored and replaced with a computed CRC. If the header packet is of type data,
		:attr:``data_sink`` will be allowed to provide a follow-up Data Packet Payload.
	data_sink: SuperSpeedStreamInterface(), input stream
		If the provided :attr:``header`` is a Data Header, and :attr:``data_sink`` is valid
		when the packet's transmission is sent, this data stream will be used to provide a
		follow-up Data Packet Payload.

	generate: Signal(), input
		Strobe; indicates that a packet should be generated.
	done: Signal(), output
		Indicates that the packet will be complete this cycle; and thus this unit will
		be ready to send another packet next cycle.
	'''

	def __init__(self):

		#
		# I/O port.
		#
		self.source    = USBRawSuperSpeedStream()

		# Packet data in.
		self.header    = HeaderPacket()
		self.data_sink = SuperSpeedStreamInterface()

		self.generate  = Signal()
		self.done      = Signal()

	def elaborate(self, platform):
		m = Module()

		# Shorthands.
		source    = self.source
		data_sink = self.data_sink

		# Latched data.
		header    = HeaderPacket()

		#
		# CRC Generators
		#

		# CRC-16, for header packets
		m.submodules.crc16 = crc16 = HeaderPacketCRC()
		m.d.comb += crc16.data_input.eq(source.data)

		# CRC-32, for payload packets
		m.submodules.crc32 = crc32 = DataPacketPayloadCRC()
		m.d.comb += [
			crc32.data_input.eq(self.data_sink.data),

			# Advance our CRC32 whenever we accept data from our sink,
			# according to how many bytes are currently valid.
			crc32.advance_word.eq((data_sink.valid == 0b1111) & data_sink.ready),
			crc32.advance_3B.eq((data_sink.valid == 0b0111) & data_sink.ready),
			crc32.advance_2B.eq((data_sink.valid == 0b0011) & data_sink.ready),
			crc32.advance_1B.eq((data_sink.valid == 0b0001) & data_sink.ready),
		]

		#
		# Packet transmitter.
		#

		# Create a time-delayed version of our data; so we can always work with data from
		# a single cycle ago. This allows us to compute our CRC before we need to send it;
		# which is important, as a CRC can start in the middle of a word (and thus needs to
		# factor in data available the same cycle as the CRC is sent).
		pipelined_data_word  = Signal.like(self.data_sink.data)
		pipelined_data_valid = Signal.like(self.data_sink.valid)

		# Store whether our packet is a ZLP.
		packet_is_zlp = Signal()

		with m.FSM(domain = 'ss'):

			# IDLE -- wait for a generate command
			with m.State('IDLE'):
				# Don't start our CRCs until we're sending the data section relevant to them.
				m.d.comb += [
					crc16.clear.eq(1),
					crc32.clear.eq(1)
				]

				# Once we have a request, latch in our data, and start sending.
				with m.If(self.generate):
					m.d.ss += header.eq(self.header)
					m.next = 'SEND_HPSTART'

			# SEND_HPSTART -- send our 'start-of-header-packet' marker.
			with m.State('SEND_HPSTART'):

				# Drive the bus with our header...
				header_data, header_ctrl = get_word_for_symbols(SHP, SHP, SHP, EPF)
				m.d.comb += [
					source.valid.eq(1),
					source.data.eq(header_data),
					source.ctrl.eq(header_ctrl),
				]

				# ... and keep driving it until it's accepted.
				with m.If(source.ready):
					m.next = 'SEND_DW0'

			# SEND_DWn -- send along the three first data words unalatered, as we don't
			# need to fill in any CRC details, here.
			data_words = [header.dw0, header.dw1, header.dw2]
			for n in range(3):
				with m.State(f'SEND_DW{n}'):

					# Drive the bus with the relevant data word...
					m.d.comb += [
						source.valid.eq(1),
						source.data.eq(data_words[n]),
						source.ctrl.eq(0),
					]

					# ... and keep driving it until it's accepted.
					with m.If(source.ready):
						m.d.comb += crc16.advance_crc.eq(1)
						m.next = f'SEND_DW{n + 1}'

			# Compose our final data word from our individual fields, filling
			with m.State('SEND_DW3'):

				# Compose our data word
				m.d.comb += [
					source.valid.eq(1),
					source.data[ 0:16].eq(crc16.crc),
					source.data[16:19].eq(header.sequence_number),
					source.data[19:22].eq(header.dw3_reserved),
					source.data[22:25].eq(header.hub_depth),
					source.data[25].eq(header.delayed),
					source.data[26].eq(header.deferred),
					source.data[27:32].eq(compute_usb_crc5(source.data[16:27])),
					source.ctrl.eq(0)
				]

				# Once our final header packet word has been sent; decide what to do next.
				with m.If(source.ready):

					# If we just sent a data packet header, and we have a valid data-stream,
					# or if we're sending a ZLP, follow on immediately with a Data Packet Payload.
					was_data_header = (header.dw0[ 0: 4] == HeaderPacketType.DATA)
					with m.If(was_data_header):
						m.d.ss += packet_is_zlp.eq(self.data_sink.valid == 0)
						m.next = 'START_DPP'

					# Otherwise, we're done!
					with m.Else():
						m.d.comb += self.done.eq(1)
						m.next = 'IDLE'

			# START_DPP -- we'll start our data packet payload with our framing;
			# and prepare to send our actual payload.
			with m.State('START_DPP'):

				# Send our start framing...
				header_data, header_ctrl = get_word_for_symbols(SDP, SDP, SDP, EPF)
				m.d.comb += [
					source.valid.eq(1),
					source.data.eq(header_data),
					source.ctrl.eq(header_ctrl),
				]

				with m.If(source.ready):

					# Special case: if we're retransmitting a data packet, we'll abort it immediately,
					# since we don't have the data buffered anywhere. Retransmission of the data payload
					# will be handled at the protocol layer.
					with m.If(header.delayed):
						m.next = 'ABORT_DPP'

					# Special case: if we're sending a ZLP, we'll jump directly to sending our CRC.
					with m.Elif(packet_is_zlp):

						# We'll treat this as though we'd just sent a full data word; as this indicates
						# to our later states that our data was word-aligned. (0B is a multiple of 4, after all.)
						m.d.ss += pipelined_data_valid.eq(0b1111)
						m.next = 'SEND_CRC'

					# Otherwise, we have some data to handle.
					with m.Else():

						# Capture the first word of our data-to-send into our pipeline...
						m.d.ss   += [
							pipelined_data_word.eq(self.data_sink.data),
							pipelined_data_valid.eq(self.data_sink.valid)
						]

						# ... and accept that data, so our sender moves on to the next word.
						m.d.comb += self.data_sink.ready.eq(1)

						# ... and as long as we didn't just capture the last word, start the actual
						# payload transmission.
						with m.If(~self.data_sink.last):
							m.next = 'SEND_PAYLOAD'

						# If we -did- just capture the last word, we can skip to our last-word handler.
						with m.Else():
							m.next = 'SEND_LAST_WORD'

			# SEND_PAYLOAD -- send the core part of our data stream.
			with m.State('SEND_PAYLOAD'):

				# We'll always send our last pipelined value; so we can always have a valid 'last word'
				# for our last word handler, which handles special cases like inserting our CRC.
				m.d.comb += [
					source.data.eq(pipelined_data_word),
					source.valid.eq(1),
				]

				# Each time one of the words we're transmitting is accepted, we'll in turn accept a new
				# word into our pipeline register.
				with m.If(source.ready):
					m.d.comb += self.data_sink.ready.eq(1)
					m.d.ss   += [
						pipelined_data_word.eq(self.data_sink.data),
						pipelined_data_valid.eq(self.data_sink.valid)
					]

					# When we're finally receiving our last packet, we're also finished computing our CRC.
					# This means we can move on safely to sending our pipelined word; knowing we're ready to
					# send part of our CRC if the last word isn't fully aligned.
					with m.If(data_sink.last):
						m.next = 'SEND_LAST_WORD'

			# SEND_LAST_WORD -- flush our pipeline; and send our final word.
			# Our final word is a special case; as it may not be a full word; in which
			# case we'll have to stick part of our CRC into it.
			with m.State('SEND_LAST_WORD'):

				# Send the final value captured from our pipeline; which we may modify slightly below.
				m.d.comb += [
					source.data.eq(pipelined_data_word),
					source.valid.eq(1),
				]

				# If we didn't capture a full word, we'll need to stick pieces of our final CRC
				# into the unused/invalid sections of our data. We'll do so, selecting our position
				# based on how many bytes of valid data we're sending.
				with m.Switch(pipelined_data_valid):

					# 3 valid bytes, 1 byte of CRC
					with m.Case(0b0111):
						m.d.comb += source.data[24:32].eq(crc32.crc[0:8])

					# 2 valid bytes, 2 bytes of CRC
					with m.Case(0b0011):
						m.d.comb += source.data[16:32].eq(crc32.crc[0:16])

					# 1 valid byte, 3 bytes of CRC
					with m.Case(0b0001):
						m.d.comb += source.data[8:32].eq(crc32.crc[0:24])

				# Once our last data word is accepted, move to sending the (remainder) of the CRC.
				with m.If(source.ready):
					m.next = 'SEND_CRC'

			# SEND_CRC -- send the CRC (or what remains of it)
			with m.State('SEND_CRC'):
				m.d.comb += source.valid.eq(1)

				# We'll need to send however much of the CRC is left; which will depend on the validity
				# of the final byte we had pipelined.
				with m.Switch(pipelined_data_valid):

					# If our data packet was word aligned, we've sent no CRC bytes.
					# Send our full word.
					with m.Case(0b1111):
						m.d.comb += [
							source.data.eq(crc32.crc),
							source.ctrl.eq(0)
						]

					# If we had three valid bytes of data last time, we've sent one byte of CRC.
					# Send the other three, followed by one END framing word.
					with m.Case(0b0111):
						m.d.comb += [
							source.data.eq(Cat(crc32.crc[8:32], END.value_const())),
							source.ctrl.eq(0b1000),
						]

					# Same, but for 2B valid and 2B CRC
					with m.Case(0b0011):
						m.d.comb += [
							source.data.eq(Cat(crc32.crc[16:32], END.value_const(repeat = 2))),
							source.ctrl.eq(0b1100),
						]

					# Same, but for 1B valid and 3B CRC
					with m.Case(0b0001):
						m.d.comb += [
							source.data.eq(Cat(crc32.crc[24:32], END.value_const(repeat = 3))),
							source.ctrl.eq(0b1110),
						]

				# Once our CRC is accepted, finish our payload.
				with m.If(source.ready):
					m.next = 'FINISH_DPP'

			# FINISH_DPP -- send our end-of-payload framing.
			with m.State('FINISH_DPP'):
				m.d.comb += source.valid.eq(1)

				# Get our data and ctrl constants for our framing.
				framing_data, framing_ctrl = get_word_for_symbols(END, END, END, EPF)

				# We'll need to send however much of the framing is left; which will depend on the
				# where our CRC ended.
				with m.Switch(pipelined_data_valid):

					# If our data packet was word aligned, we've sent no CRC bytes.
					# Send our full word.
					with m.Case(0b1111):
						m.d.comb += [
							source.data.eq(framing_data),
							source.ctrl.eq(framing_ctrl),
						]

					# If we had three valid bytes of data last time, we've sent one byte of framing.
					# Send the other three, followed by zeroes (logical IDL).
					with m.Case(0b0111):
						m.d.comb += [
							source.data.eq(framing_data[8:]),
							source.ctrl.eq(framing_ctrl[1:]),
						]

					# Same, but 2B frame and 2B IDL.
					with m.Case(0b0011):
						m.d.comb += [
							source.data.eq(framing_data[16:]),
							source.ctrl.eq(framing_ctrl[ 2:]),
						]

					# Same, but 1B frame and 3B IDL.
					with m.Case(0b0001):
						m.d.comb += [
							source.data.eq(framing_data[24:]),
							source.ctrl.eq(framing_ctrl[ 3:]),
						]

				# Once our CRC is accepted, finish our payload.
				with m.If(source.ready):
					m.d.comb += self.done.eq(1)
					m.next = 'IDLE'

			# ABORT_DPP -- send our abort-of-payload framing.
			with m.State('ABORT_DPP'):

				# Send our abort framing...
				framing_data, framing_ctrl = get_word_for_symbols(EDB, EDB, EDB, EPF)
				m.d.comb += [
					source.valid.eq(1),
					source.data.eq(framing_data),
					source.ctrl.eq(framing_ctrl),
				]

				# Once our framing is accepted, finish our payload.
				with m.If(source.ready):
					m.d.comb += self.done.eq(1)
					m.next = 'IDLE'

		return m

class PacketTransmitter(Elaboratable):
	''' Transmitter-side Header Packet logic.

	This module handles all header-packet-transmission related logic for the link layer; including
	header packet generation, partner buffer tracking / flow control, and link command reception.

	Attributes
	----------
	sink: USBRawSuperSpeedStream(), input stream [monitor only]
		Stream that monitors the data received on the physical layer for link commands.
	source: USBRawSuperSpeedStream(), output stream
		Stream that carries our transmissions down to the physical layer.

	enable: Signal(), input
		When asserted, this unit will be enabled; and will be allowed to start transmitting.

	queue: HeaderQueue(), input stream
		Stream of header packets received received from the protocol layer to be transmitted.

	retry_received: Signal(), output
		Strobe; pulsed high when we receive a link retry request.
	retry_required: Signal(), output
		Strobe; pulsed high when we need to send a retry request.

	lgo_received: Signal(), output
		Strobe; indicates that we've received a request to move to a new power state.
	lgo_target; Signal(2), output
		Indicates the power-state associated with a given LGO event; valid when :attr:``lgo_received``
		is asserted.

	recovery_required: Signal(), output
		Strobe; pulsed when a condition that requires link recovery occurs.
	'''

	SEQUENCE_NUMBER_WIDTH = 3

	CREDIT_TIMEOUT = 5e-3

	def __init__(self, *, buffer_count = 4, ss_clock_frequency = 125e6):
		self._buffer_count    = buffer_count
		self._clock_frequency = ss_clock_frequency

		#
		# I/O port
		#
		self.sink                  = USBRawSuperSpeedStream()
		self.source                = USBRawSuperSpeedStream()

		# Simple controls.
		self.enable                = Signal()
		self.usb_reset             = Signal()
		self.bringup_complete      = Signal()

		# Protocol layer interface.
		self.queue                 = HeaderQueue()
		self.data_sink             = SuperSpeedStreamInterface()

		# Event interface.
		self.link_command_received = Signal()
		self.retry_received        = Signal()
		self.retry_required        = Signal()
		self.lrty_pending          = Signal()
		self.recovery_required     = Signal()

		self.lgo_received          = Signal()
		self.lgo_target            = Signal(2)

		# Debug information.
		self.credits_available     = Signal(range(self._buffer_count + 1))
		self.packets_to_send       = Signal(range(self._buffer_count + 1))

	def elaborate(self, platform):
		m = Module()

		#
		# Credit tracking.
		#

		# Keep track of how many transmit credits we currently have.
		credits_available    = Signal(range(self._buffer_count + 1))
		credit_received      = Signal()
		credit_consumed      = Signal()

		with m.If(credit_received & ~credit_consumed):
			m.d.ss += credits_available.eq(credits_available + 1)
		with m.Elif(credit_consumed & ~credit_received):
			m.d.ss += credits_available.eq(credits_available - 1)

		#
		# Task 'queues'.
		#

		# Control signals.
		retire_packet     = Signal()

		# Pending packet count.
		packets_to_send      = Signal(range(self._buffer_count + 1))
		enqueue_send         = Signal()
		dequeue_send         = Signal()

		# Un-retired packet count.
		packets_awaiting_ack = Signal(range(self._buffer_count + 1))

		# If we need to retry sending our packets, we'll need to reset our pending packet count.
		# Otherwise, we increment and decrement our 'to send' counts normally.
		with m.If(self.retry_required):
			m.d.ss += packets_to_send.eq(packets_awaiting_ack)
		with m.Elif(enqueue_send & ~dequeue_send):
			m.d.ss += packets_to_send.eq(packets_to_send + 1)
		with m.Elif(dequeue_send & ~enqueue_send):
			m.d.ss += packets_to_send.eq(packets_to_send - 1)

		# Track how many packets are yet to be retired.
		with m.If(enqueue_send & ~retire_packet):
			m.d.ss += packets_awaiting_ack.eq(packets_awaiting_ack + 1)
		with m.Elif(retire_packet & ~enqueue_send & (packets_awaiting_ack != 0)):
			m.d.ss += packets_awaiting_ack.eq(packets_awaiting_ack - 1)

		#
		# Header Packet Buffers
		#

		# Track:
		# - which buffer should be filled next
		# - which buffer we should send from next
		# - which buffer we've last acknowledged
		read_pointer      = Signal(range(self._buffer_count))
		write_pointer     = Signal.like(read_pointer)
		ack_pointer       = Signal.like(read_pointer)

		# Create buffers to receive any incoming header packets.
		buffers = Array(HeaderPacket() for _ in range(self._buffer_count))

		# If we need to retry sending our packets, we'll need to start reading
		# again from the last acknowledged packet; so we'll reset our read pointer.
		with m.If(self.retry_required):
			m.d.ss += read_pointer.eq(ack_pointer)
		with m.Elif(dequeue_send):
			m.d.ss += read_pointer.eq(read_pointer + 1)

		# Last ACK'd buffer / packet retirement tracker.
		with m.If(retire_packet):
			m.d.ss += ack_pointer.eq(ack_pointer + 1)

		#
		# Packet acceptance (protocol layer -> link layer).
		#

		# Keep track of the next sequence number we'll need to assign.
		transmit_sequence_number = Signal(self.SEQUENCE_NUMBER_WIDTH)

		# If we have link credits available, we're able to accept data from the protocol layer.
		m.d.comb += self.queue.ready.eq(self.bringup_complete & (credits_available != 0))

		# If the protocol layer is handing us a packet...
		with m.If(self.queue.valid & self.queue.ready):
			# ... consume a credit, as we're going to be using up a receiver buffer, and
			# schedule sending of that packet.
			m.d.comb += [
				credit_consumed.eq(1),
				enqueue_send.eq(1)
			]

			# Assign the packet a sequence number, and capture it into the buffer for transmission.
			# [USB3.0r1: 7.2.4.1.1]: 'A header packet that is re-transmitted shall maintain its
			# originally assigned Header Sequence Number.'
			m.d.ss += [
				buffers[write_pointer].eq(self.queue.header),
				buffers[write_pointer].sequence_number.eq(transmit_sequence_number),
				write_pointer.eq(write_pointer + 1),
				transmit_sequence_number.eq(transmit_sequence_number + 1)
			]

		#
		# Packet delivery (link layer -> physical layer)
		#
		m.submodules.packet_tx = packet_tx = RawPacketTransmitter()
		m.d.comb += [
			packet_tx.header.eq(buffers[read_pointer]),
			packet_tx.data_sink.stream_eq(self.data_sink),
			self.source.stream_eq(packet_tx.source)
		]

		# Keep track of whether a retry has been requested.
		retry_pending = Signal()
		with m.If(self.retry_required):
			m.d.ss += retry_pending.eq(1)

		with m.FSM(domain = 'ss'):

			# DISPATCH_PACKET -- wait packet transmissions to be scheduled, and prepare
			# our local transmitter with the proper data to send them.
			with m.State('DISPATCH_PACKET'):

				# If we have packets to send, pass them to our transmitter.
				with m.If(self.bringup_complete & (packets_to_send != 0)):

					with m.If(~retry_pending):
						# Wait until the packet is sent.
						m.next = 'WAIT_FOR_SEND'

					with m.Else():
						# Wait until all of the non-acknowledged packets are retransmitted.
						m.next = 'WAIT_FOR_RETRY'

			# WAIT_FOR_SEND -- we've now dispatched our packet; and we're ready to wait for it to be sent.
			with m.State('WAIT_FOR_SEND'):
				m.d.comb += packet_tx.generate.eq(1)

				# We're done with this packet.
				with m.If(packet_tx.done):

					# If we received an LBAD in the meantime, our read pointer and counter are already
					# set up for retransmission; don't touch them.
					with m.If(~retry_pending):
						m.d.comb += dequeue_send.eq(1)

					# Handle the next packet, or wait for one.
					m.next = 'DISPATCH_PACKET'

			# WAIT_FOR_RETRY -- we're retransmitting all of the non-acknowledged packets, with the DL bit set;
			# but only after the receiver transmits LRTY.
			with m.State('WAIT_FOR_RETRY'):
				m.d.comb += packet_tx.header.delayed.eq(1)
				m.d.comb += packet_tx.generate.eq(~self.lrty_pending)

				# We're done with this packet.
				with m.If(packet_tx.done):
					m.d.comb += dequeue_send.eq(1)

					# If this was the last packet to retransmit, we're done handling this LBAD.
					with m.If(packets_to_send == 1):
						m.d.ss += retry_pending.eq(0)
						m.next = 'DISPATCH_PACKET'

		#
		# Link Command Handling
		#

		# Core link command receiver.
		m.submodules.lc_detector = lc_detector = LinkCommandDetector()
		m.d.comb += [
			lc_detector.sink.tap(self.sink),
			self.link_command_received.eq(lc_detector.new_command)
		]

		# Keep track of which credit we expect to get back next.
		next_expected_credit = Signal(range(self._buffer_count))

		# Keep track of what sequence number we expect to have ACK'd next.
		next_expected_ack_number = Signal(self.SEQUENCE_NUMBER_WIDTH, reset = -1)

		# Handle link commands as we receive them.
		with m.If(lc_detector.new_command):
			with m.Switch(lc_detector.command):

				#
				# Link Credit Reception
				#
				with m.Case(LinkCommand.LCRD):

					# If the credit matches the sequence we're expecting, we can accept it!
					with m.If(next_expected_credit == lc_detector.subtype):
						m.d.comb += credit_received.eq(1)

						# Next time, we'll expect the next credit in the sequence.
						m.d.ss += next_expected_credit.eq(next_expected_credit + 1)

					# Otherwise, we've lost synchronization. We'll need to trigger link recovery.
					with m.Else():
						m.d.comb += self.recovery_required.eq(1)

				#
				# Packet Acknowledgement
				#
				with m.Case(LinkCommand.LGOOD):

					# If we've received a Header Sequence Number Advertisement, update our sequence
					# numbers, and indicate we're done with bringup.
					with m.If(~self.bringup_complete):
						m.d.ss += [
							self.bringup_complete.eq(1),

							next_expected_ack_number.eq(lc_detector.subtype + 1),
							transmit_sequence_number.eq(lc_detector.subtype + 1)
						]

					# If the credit matches the sequence we're expecting, we can accept it!
					with m.Elif(next_expected_ack_number == lc_detector.subtype):
						m.d.comb += retire_packet.eq(1)

						# Next time, we'll expect the next credit in the sequence.
						m.d.ss += next_expected_ack_number.eq(next_expected_ack_number + 1)

					# Otherwise, if we're expecting a packet, we've lost synchronization.
					# We'll need to trigger link recovery.
					with m.Else():
						m.d.comb += self.recovery_required.eq(1)

				#
				# Packet Negative Acknowledgement
				#
				with m.Case(LinkCommand.LBAD):

					# LBADs aren't sequenced; instead, they require a retry of all unacknowledged
					# (unretired) packets. Mark ourselves as requiring a retry; which should trigger
					# our internal logic to execute a retry.
					m.d.comb += self.retry_required.eq(1)

				#
				# Link Partner Retrying Send
				#
				with m.Case(LinkCommand.LRTY):

					# If we see an LRTY, it's an indication that our link partner is performing a
					# retried send. This information is handled by the Receiver, so we'll forward it along.
					m.d.comb += self.retry_received.eq(1)

				#
				# Link Power State transition request.
				#
				with m.Case(LinkCommand.LGO_U):

					# We don't handle LGO requests locally; so instead, we'll pass this back to the link layer.
					m.d.comb += [
						self.lgo_received.eq(1),
						self.lgo_target.eq(lc_detector.subtype)
					]

		#
		# Header Packet Timer
		#

		# To ensure that a header packet buffer is never held by the receiver for too long
		# the USB3 specification requires us to automatically enter recovery if a credit is
		# outstanding for more than 5ms [USB3.2r1: 7.2.4.1.13]. We'll create a timer that can
		# count to this timeout.
		credit_timeout_cycles = int(self.CREDIT_TIMEOUT * self._clock_frequency + 1)
		pending_hp_timer = Signal(range(credit_timeout_cycles + 1))

		# Each time we receive a link credit and retire its packet, we'll re-start our timer.
		with m.If(retire_packet):
			m.d.ss += pending_hp_timer.eq(0)

		# If we don't have any outstanding packets, we'll clear our timer.
		with m.Elif((packets_awaiting_ack == 0) | ~self.enable):
			m.d.ss += pending_hp_timer.eq(0)

		# Otherwise, we'll count how many cycles we've had a credit outstanding for.
		with m.Else():
			m.d.ss += pending_hp_timer.eq(pending_hp_timer + 1)

		# If we ever reach our timeout, we'll need to trigger recovery.
		with m.If(pending_hp_timer == credit_timeout_cycles):
			m.d.comb += self.recovery_required.eq(1)

		#
		# Reset Handling
		#
		with m.If(~self.enable):
			m.d.ss += [
				self.bringup_complete.eq(0),

				next_expected_credit.eq(0),
				credits_available.eq(0),

				packets_to_send.eq(0),
				packets_awaiting_ack.eq(0),
				read_pointer.eq(0),
				write_pointer.eq(0),
				ack_pointer.eq(0),
				retry_pending.eq(0),
			]

		#
		# Debug outputs.
		#
		m.d.comb += [
			self.credits_available.eq(credits_available),
			self.packets_to_send.eq(packets_to_send)
		]

		return m
