def elaborate(self, platform): m = Module() data_word = self.sink.data ctrl_word = self.sink.ctrl # Capture the previous data word; so we have a record of eight consecutive signals. last_word = Past(self.sink.data) last_ctrl = Past(self.sink.ctrl) # Logical idle descrambles to the raw data value zero; so we only need to validate that # the last and current words are both zeroes. last_word_was_idle = (last_word == 0) & (last_ctrl == 0) current_word_is_idle = (data_word == 0) & (ctrl_word == 0) m.d.comb += [ self.idle_detected.eq(last_word_was_idle & current_word_is_idle) ] # # Handshake condition detector. # seen_idle = Signal() enable_counter = Signal(range(self.RX_CYCLES_REQUIRED + 1)) with m.If(self.enable): # Keep track of how many consecutive cycles we're enabled for; as we must # send logical idle for at least 16B in order to complete the Idle handshake. with m.If(enable_counter < self.RX_CYCLES_REQUIRED): m.d.ss += enable_counter.eq(enable_counter + 1) # Keep track of whether we've ever seen eight consecutive cycles of idle. with m.If(self.idle_detected): m.d.ss += seen_idle.eq(1) # Our handshake is complete once we've sent logical idle for at least 16 bytes, # and we've seen at least eight byte send_condition_met = enable_counter == self.RX_CYCLES_REQUIRED m.d.comb += self.idle_handshake_complete.eq(seen_idle & send_condition_met) # When we're not idle, clear all of our state. with m.Else(): m.d.ss += [enable_counter.eq(0), seen_idle.eq(0)] return m
def elaborate(self, platform): m = Module() data_word = self.sink.data ctrl_word = self.sink.ctrl # Capture the previous data word; so we have a record of eight consecutive signals. last_word = Past(self.sink.data) last_ctrl = Past(self.sink.ctrl) # Logical idle descrambles to the raw data value zero; so we only need to validate that # the last and current words are both zeroes. last_word_was_idle = (last_word == 0) & (last_ctrl == 0) current_word_is_idle = (data_word == 0) & (ctrl_word == 0) m.d.comb += [ self.idle_detected.eq(last_word_was_idle & current_word_is_idle) ] return m
def elaborate(self, platform): m = Module() shared = self.shared # # Pass through signals being routed -to- our pre-mux interfaces. # for interface in self._interfaces: m.d.comb += [ # CRC and timer shared signals interface. interface.data_crc.crc .eq(shared.data_crc.crc), interface.timer.tx_allowed .eq(shared.timer.tx_allowed), interface.timer.tx_timeout .eq(shared.timer.tx_timeout), interface.timer.rx_timeout .eq(shared.timer.rx_timeout), # Detectors. shared.handshakes_in .connect(interface.handshakes_in), shared.tokenizer .connect(interface.tokenizer), # Rx interface. shared.rx .connect(interface.rx), interface.rx_complete .eq(shared.rx_complete), interface.rx_ready_for_response .eq(shared.rx_ready_for_response), interface.rx_invalid .eq(shared.rx_invalid), interface.rx_pid_toggle .eq(shared.rx_pid_toggle), # State signals. interface.speed .eq(shared.speed), interface.active_config .eq(shared.active_config), interface.active_address .eq(shared.active_address) ] # # Multiplex the signals being routed -from- our pre-mux interface. # self._multiplex_signals(m, when='address_changed', multiplex=['address_changed', 'new_address'] ) self._multiplex_signals(m, when='config_changed', multiplex=['config_changed', 'new_config'] ) # Connect up our transmit interface. m.submodules.tx_mux = tx_mux = OneHotMultiplexer( interface_type=USBInStreamInterface, mux_signals=('payload',), or_signals=('valid', 'first', 'last'), pass_signals=('ready',) ) tx_mux.add_interfaces(i.tx for i in self._interfaces) m.d.comb += self.shared.tx.connect(tx_mux.output) # OR together all of our handshake-generation requests... self.or_join_interface_signals(m, lambda interface : interface.handshakes_out.ack) self.or_join_interface_signals(m, lambda interface : interface.handshakes_out.nak) self.or_join_interface_signals(m, lambda interface : interface.handshakes_out.stall) # ... our CRC start signals... self.or_join_interface_signals(m, lambda interface : interface.data_crc.start) # ... and our timer start signals. self.or_join_interface_signals(m, lambda interface : interface.timer.start) # Finally, connect up our transmit PID select. conditional = m.If # We'll connect our PID toggle to whichever interface has a valid transmission going. for interface in self._interfaces: with conditional(interface.tx.valid | Past(interface.tx.valid)): m.d.comb += shared.tx_pid_toggle.eq(interface.tx_pid_toggle) conditional = m.Elif return m
def elaborate(self, platform): m = Module() # Buffer our stream inputs here to improve timing. stream_in_data = Signal.like(self.sink.data) stream_in_ctrl = Signal.like(self.sink.ctrl) stream_in_valid = Signal.like(self.sink.valid) m.d.sync += [ stream_in_data.eq(self.sink.data), stream_in_ctrl.eq(self.sink.ctrl), stream_in_valid.eq(self.sink.valid), ] # Aliases. stream_in = self.sink if self._flip_bytes: stream_in_data = stream_in.data.rotate_right( (self._words_in // 2) * 8) stream_in_ctrl = stream_in.ctrl.rotate_right(self._words_in // 2) else: stream_in_data = stream_in.data stream_in_ctrl = stream_in.ctrl # If our output domain is the same as our input domain, we'll directly drive our output stream. # Otherwise, we'll drive an internal signal; and then cross that into our output domain. if self._output_domain == self._input_domain: stream_out = self.source else: stream_out = USBRawSuperSpeedStream() # Create proxies that allow us access to the upper and lower halves of our output data stream. data_out_halves = Array( stream_out.data.word_select(i, self._data_bits_in) for i in range(2)) ctrl_out_halves = Array( stream_out.ctrl.word_select(i, self._ctrl_bits_in) for i in range(2)) # Word select -- selects whether we're targeting the upper or lower half of the output word. # Toggles every input-domain cycle. targeting_upper_half = Signal(reset=1 if self._flip_bytes else 0) m.d.sync += targeting_upper_half.eq(~targeting_upper_half) # Pass through our data and control every cycle. m.d.sync += [ data_out_halves[targeting_upper_half].eq(stream_in_data), ctrl_out_halves[targeting_upper_half].eq(stream_in_ctrl), ] # Set our valid signal high only if both the current and previously captured word are valid. m.d.comb += [ stream_out.valid.eq( stream_in.valid & Past(stream_in.valid, domain=self._input_domain)) ] if self._input_domain != self._output_domain: in_domain_signals = Cat(stream_out.data, stream_out.ctrl, stream_out.valid) out_domain_signals = Cat(self.source.data, self.source.ctrl, self.source.valid) # Create our async FIFO... m.submodules.cdc = fifo = AsyncFIFOBuffered( width=len(in_domain_signals), depth=8, w_domain="sync", r_domain=self._output_domain) m.d.comb += [ # ... fill it from our in-domain stream... fifo.w_data.eq(in_domain_signals), fifo.w_en.eq(targeting_upper_half), # ... and output it into our output stream. out_domain_signals.eq(fifo.r_data), self.source.valid.eq(fifo.r_level > 2), fifo.r_en.eq(1), ] # If our source domain isn't `sync`, translate `sync` to the proper domain name. if self._input_domain != "sync": m = DomainRenamer({'sync': self._input_domain})(m) return m
def elaborate(self, platform): m = Module() # If we're receiving data from an domain other than our output domain, # cross it over nicely. if self._input_domain != self._output_domain: stream_in = USBRawSuperSpeedStream(payload_words=self._words_in) in_domain_signals = Cat( self.sink.data, self.sink.ctrl, ) out_domain_signals = Cat( stream_in.data, stream_in.ctrl, ) # Advance our FIFO only ever other cycle. advance_fifo = Signal() m.d.tx += advance_fifo.eq(~advance_fifo) # Create our async FIFO... m.submodules.cdc = fifo = AsyncFIFOBuffered( width=len(in_domain_signals), depth=8, w_domain=self._input_domain, r_domain="tx") m.d.comb += [ # ... fill it from our in-domain stream... fifo.w_data.eq(in_domain_signals), fifo.w_en.eq(1), self.sink.ready.eq(1), # ... and output it into our output stream. out_domain_signals.eq(fifo.r_data), stream_in.valid.eq(fifo.r_rdy), fifo.r_en.eq(advance_fifo), ] # Otherwise, use our data-stream directly. else: stream_in = self.sink m.d.comb += self.sink.ready.eq(1) # Word select -- selects whether we're targeting the upper or lower half of the input word. # Toggles every input-domain cycle. next_half_targeted = Signal() targeting_upper_half = Signal() # If our data has just changed, we should always be targeting the upper word. # This "locks" us to the data's changes. data_changed = stream_in.data != Past(stream_in.data, domain="tx") ctrl_changed = stream_in.ctrl != Past(stream_in.ctrl, domain="tx") with m.If(data_changed | ctrl_changed): m.d.comb += targeting_upper_half.eq(1 if self._flip_bytes else 0) m.d.tx += next_half_targeted.eq(0 if self._flip_bytes else 1) with m.Else(): m.d.comb += targeting_upper_half.eq(next_half_targeted) m.d.tx += next_half_targeted.eq(~next_half_targeted) # If we're flipping the bytes in our output stream (e.g. to maintain endianness), # create a flipped version; otherwise, use our output stream directly. stream_out = self.source # Create proxies that allow us access to the upper and lower halves of our input data stream. data_in_halves = Array( stream_in.data.word_select(i, len(stream_in.data) // 2) for i in range(2)) ctrl_in_halves = Array( stream_in.ctrl.word_select(i, len(stream_in.ctrl) // 2) for i in range(2)) # Pass through our data and control every cycle. # This is registered to loosen timing. if self._flip_bytes: stream_out_data = data_in_halves[targeting_upper_half] stream_out_ctrl = ctrl_in_halves[targeting_upper_half] m.d.tx += [ stream_out.data.eq( stream_out_data.rotate_right(len(stream_out_data) // 2)), stream_out.ctrl.eq( stream_out_ctrl.rotate_right(len(stream_out_ctrl) // 2)), stream_out.valid.eq(1), ] else: m.d.tx += [ stream_out.data.eq(data_in_halves[targeting_upper_half]), stream_out.ctrl.eq(ctrl_in_halves[targeting_upper_half]), stream_out.valid.eq(1), ] # If our output domain isn't `sync`, translate `sync` to the proper domain name. if self._output_domain != "tx": m = DomainRenamer({'tx': self._output_domain})(m) return m
def elaborate(self, platform): m = Module() # Aliases stream_in = self.sink stream_out = self.source # Values from previous cycles. previous_data = Past(stream_in.data, domain="ss") previous_ctrl = Past(stream_in.ctrl, domain="ss") # Alignment register: stores how many words the data must be shifted by in order to # have correctly aligned data. shift_to_apply = Signal(range(4)) # # Alignment shift register. # # To align our data, we'll create a conglomeration of two consecutive words; # and then select the chunk between those words that has the alignment correct. # (These words should always be in chronological order; so we'll need different # orders for big endian and little endian output.) if self._is_big_endian: data = Cat(stream_in.data, previous_data) ctrl = Cat(stream_in.ctrl, previous_ctrl) else: data = Cat(previous_data, stream_in.data) ctrl = Cat(previous_ctrl, stream_in.ctrl) # Create two multiplexers that allow us to select from each of our four possible alignments. shifted_data_slices = Array(data[8 * i:] for i in range(4)) shifted_ctrl_slices = Array(ctrl[i:] for i in range(4)) # # Alignment detection. # # Our aligner is simple: we'll search for the start of a TS1/TS2 training set; which we know # starts with a burst of four consecutive commas. four_comma_data = Repl(COM.value_const(), 4) four_comma_ctrl = Repl(COM.ctrl_const(), 4) # We'll check each possible alignment to see if it would produce a valid start-of-TS1/TS2. possible_alignments = len(shifted_data_slices) for i in range(possible_alignments): data_matches = (shifted_data_slices[i][0:32] == four_comma_data) ctrl_matches = (shifted_ctrl_slices[i][0:4] == four_comma_ctrl) # If it would, we'll accept that as our alignment going forward. with m.If(data_matches & ctrl_matches): m.d.ss += shift_to_apply.eq(i) # # Alignment application. # # Grab the shifted data/ctrl associated with our alignment. m.d.ss += [ stream_out.data.eq(shifted_data_slices[shift_to_apply]), stream_out.ctrl.eq(shifted_ctrl_slices[shift_to_apply]), # We're part of a chain that always produces data (currently); # so for now, we'll always indicate our data is valid. This may # need to be changed if we add in nicer CTC. stream_out.valid.eq(1), # Debug output. self.alignment_offset.eq(shift_to_apply) ] return m
def elaborate(self, platform): m = Module() # Aliases stream_in = self.sink stream_out = self.source # Values from previous cycles. previous_data = Past(stream_in.data, domain="ss") previous_ctrl = Past(stream_in.ctrl, domain="ss") # Alignment register: stores how many words the data must be shifted by in order to # have correctly aligned data. shift_to_apply = Signal(range(4)) # # Alignment detector. # if self._is_big_endian: alignment_byte_precedence = reversed(range(4)) else: alignment_byte_precedence = range(4) # Apply new alignments only if we're the first seen COM (since USB3 packets can contain multiple # consecutive COMs), and if alignment is enabled. following_data_byte = (previous_ctrl == 0) with m.If(self.align & following_data_byte): # Detect any alignment markers by looking for a COM signal in any of the four positions. for i in alignment_byte_precedence: data_matches = (stream_in.data.word_select(i, 8) == COM.value) ctrl_matches = stream_in.ctrl[i] # If the current position has a comma in it, mark this as our alignment position; # and compute how many times we'll need to rotate our data _right_ in order to achieve # proper alignment. with m.If(data_matches & ctrl_matches): if self._is_big_endian: m.d.ss += [ shift_to_apply.eq(3 - i), stream_out.valid.eq(shift_to_apply == (3 - i)) ] else: m.d.ss += [ shift_to_apply.eq(i), stream_out.valid.eq(shift_to_apply == i) ] # # Aligner. # # To align our data, we'll create a conglomeration of two consecutive words; # and then select the chunk between those words that has the alignment correct. # (These words should always be in chronological order; so we'll need different # orders for big endian and little endian output.) if self._is_big_endian: data = Cat(stream_in.data, previous_data) ctrl = Cat(stream_in.ctrl, previous_ctrl) else: data = Cat(previous_data, stream_in.data) ctrl = Cat(previous_ctrl, stream_in.ctrl) # Create two multiplexers that allow us to select from each of our four possible # alignments... shifted_data_slices = Array(data[8 * i:] for i in range(4)) shifted_ctrl_slices = Array(ctrl[i:] for i in range(4)) # ... and output our data accordingly. m.d.ss += [ stream_out.data.eq(shifted_data_slices[shift_to_apply]), stream_out.ctrl.eq(shifted_ctrl_slices[shift_to_apply]), # Debug output. self.alignment_offset.eq(shift_to_apply) ] return m
def elaborate(self, platform): m = Module() # # Clock/Data recovery. # clock_data_recovery = RxClockDataRecovery(self.i_usbp, self.i_usbn) m.submodules.clock_data_recovery = clock_data_recovery m.d.comb += self.o_bit_strobe.eq(clock_data_recovery.line_state_valid) # # NRZI decoding # m.submodules.nrzi = nrzi = RxNRZIDecoder() m.d.comb += [ nrzi.i_valid.eq(self.o_bit_strobe), nrzi.i_dj.eq(clock_data_recovery.line_state_dj), nrzi.i_dk.eq(clock_data_recovery.line_state_dk), nrzi.i_se0.eq(clock_data_recovery.line_state_se0), ] # # Packet boundary detection. # m.submodules.detect = detect = RxPacketDetect() m.d.comb += [ detect.i_valid.eq(nrzi.o_valid), detect.i_se0.eq(nrzi.o_se0), detect.i_data.eq(nrzi.o_data), ] # # Bitstuff remover. # m.submodules.bitstuff = bitstuff = \ ResetInserter(~detect.o_pkt_active)(RxBitstuffRemover()) m.d.comb += [ bitstuff.i_valid.eq(nrzi.o_valid), bitstuff.i_data.eq(nrzi.o_data), self.o_receive_error.eq(bitstuff.o_error) ] # # 1bit->8bit (1byte) gearing # m.submodules.shifter = shifter = RxShifter(width=8) m.d.comb += [ shifter.reset.eq(detect.o_pkt_end), shifter.i_data.eq(bitstuff.o_data), shifter.i_valid.eq(~bitstuff.o_stall & Past(detect.o_pkt_active, domain="usb_io")), ] # # Clock domain crossing. # flag_start = Signal() flag_end = Signal() flag_valid = Signal() m.submodules.payload_fifo = payload_fifo = AsyncFIFOBuffered( width=8, depth=4, r_domain="usb", w_domain="usb_io") m.d.comb += [ payload_fifo.w_data.eq(shifter.o_data[::-1]), payload_fifo.w_en.eq(shifter.o_put), self.o_data_payload.eq(payload_fifo.r_data), self.o_data_strobe.eq(payload_fifo.r_rdy), payload_fifo.r_en.eq(1) ] m.submodules.flags_fifo = flags_fifo = AsyncFIFOBuffered( width=2, depth=4, r_domain="usb", w_domain="usb_io") m.d.comb += [ flags_fifo.w_data[1].eq(detect.o_pkt_start), flags_fifo.w_data[0].eq(detect.o_pkt_end), flags_fifo.w_en.eq(detect.o_pkt_start | detect.o_pkt_end), flag_start.eq(flags_fifo.r_data[1]), flag_end.eq(flags_fifo.r_data[0]), flag_valid.eq(flags_fifo.r_rdy), flags_fifo.r_en.eq(1), ] # Packet flag signals (in 12MHz domain) m.d.comb += [ self.o_pkt_start.eq(flag_start & flag_valid), self.o_pkt_end.eq(flag_end & flag_valid), ] with m.If(self.o_pkt_start): m.d.usb += self.o_pkt_in_progress.eq(1) with m.Elif(self.o_pkt_end): m.d.usb += self.o_pkt_in_progress.eq(0) return m
def elaborate(self, platform): m = Module() # Mark ourselves as always ready for new data. m.d.comb += self.sink.ready.eq(1) # Values from previous cycles. previous_data = Signal.like(self.sink.data) previous_ctrl = Signal.like(self.sink.ctrl) with m.If(self.sink.valid): m.d.ss += [ previous_data.eq(self.sink.data), previous_ctrl.eq(self.sink.ctrl), ] previous_valid = Past(self.sink.valid, domain="ss") # Alignment register: stores how many words the data must be shifted by in order to # have correctly aligned data. shift_to_apply = Signal(range(4)) # # Alignment shift register. # # To align our data, we'll create a conglomeration of two consecutive words; # and then select the chunk between those words that has the alignment correct. data = Cat(previous_data, self.sink.data) ctrl = Cat(previous_ctrl, self.sink.ctrl) # Create two multiplexers that allow us to select from each of our four possible alignments. shifted_data_slices = Array(data[8 * i:] for i in range(4)) shifted_ctrl_slices = Array(ctrl[i:] for i in range(4)) # # Alignment detection. # # Our aligner is simple: we'll search for the start of a TS1/TS2 training set; which we know # starts with a burst of four consecutive commas. four_comma_data = Repl(COM.value_const(), 4) four_comma_ctrl = Repl(COM.ctrl_const(), 4) # We'll check each possible alignment to see if it would produce a valid start-of-TS1/TS2; # ignoring any words not marked as valid. with m.If(self.sink.valid): possible_alignments = len(shifted_data_slices) for i in range(possible_alignments): data_matches = ( shifted_data_slices[i][0:32] == four_comma_data) ctrl_matches = (shifted_ctrl_slices[i][0:4] == four_comma_ctrl) # If it would, we'll accept that as our alignment going forward. with m.If(data_matches & ctrl_matches): m.d.ss += shift_to_apply.eq(i) # # Alignment application. # # Grab the shifted data/ctrl associated with our alignment. m.d.ss += [ self.source.data.eq(shifted_data_slices[shift_to_apply]), self.source.ctrl.eq(shifted_ctrl_slices[shift_to_apply]), self.source.valid.eq(self.sink.valid), self.alignment_offset.eq(shift_to_apply), ] return m