Exemplo n.º 1
0
    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 = ResetInserter(self.reset)(
            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
Exemplo n.º 2
0
    def elaborate(self, platform: Platform) -> Module:
        m = Module()

        # Do TX CDC
        # FFSynchronizer
        if False:
            m.submodules += FFSynchronizer(Cat(self.tx_symbol,
                                               self.tx_set_disp, self.tx_disp,
                                               self.tx_e_idle),
                                           Cat(self.__lane.tx_symbol,
                                               self.__lane.tx_set_disp,
                                               self.__lane.tx_disp,
                                               self.__lane.tx_e_idle),
                                           o_domain="tx",
                                           stages=4)

        # No CDC
        # TODO: Check if this actually works
        if False:
            m.d.comb += Cat(self.__lane.tx_symbol, self.__lane.tx_set_disp,
                            self.__lane.tx_disp, self.__lane.tx_e_idle).eq(
                                Cat(self.tx_symbol, self.tx_set_disp,
                                    self.tx_disp, self.tx_e_idle))

        # AsyncFIFOBuffered
        if True:
            tx_fifo = m.submodules.tx_fifo = AsyncFIFOBuffered(
                width=self.ratio * 12, depth=8, r_domain="tx", w_domain="rx")
            m.d.comb += tx_fifo.w_data.eq(
                Cat(self.tx_symbol, self.tx_set_disp, self.tx_disp,
                    self.tx_e_idle))
            m.d.comb += Cat(self.__lane.tx_symbol, self.__lane.tx_set_disp,
                            self.__lane.tx_disp,
                            self.__lane.tx_e_idle).eq(tx_fifo.r_data)
            m.d.comb += tx_fifo.r_en.eq(1)
            m.d.comb += tx_fifo.w_en.eq(1)

        # Testing symbols
        if False:
            m.d.comb += self.__lane.tx_symbol.eq(Cat(Ctrl.COM, D(10, 2)))

        self.slip = SymbolSlip(symbol_size=10,
                               word_size=self.__lane.ratio,
                               comma=Cat(Ctrl.COM, 1))
        m.submodules += self.slip

        m.d.comb += [
            self.slip.en.eq(self.rx_align),
            self.slip.i.eq(
                Cat((self.__lane.rx_symbol.word_select(n, 9),
                     self.__lane.rx_valid[n])
                    for n in range(self.__lane.ratio))),
            self.rx_symbol.eq(
                Cat(
                    Part(self.slip.o, 10 * n, 9)
                    for n in range(self.__lane.ratio))),
            self.rx_valid.eq(
                Cat(self.slip.o[10 * n + 9]
                    for n in range(self.__lane.ratio))),
        ]
        return m
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
    def elaborate(self, platform: Platform) -> Module:
        m = Module()

        m.submodules.serdes = serdes = LatticeECP5PCIeSERDES(1)
        m.submodules += self.lane

        m.domains.rxf = ClockDomain()
        m.domains.txf = ClockDomain()
        m.d.comb += [
            #ClockSignal("sync").eq(serdes.refclk),
            ClockSignal("rxf").eq(serdes.rx_clk),
            ClockSignal("txf").eq(serdes.tx_clk),
        ]

        platform.add_clock_constraint(
            self.rx_clk, 125e6
        )  # For NextPNR, set the maximum clock frequency such that errors are given
        platform.add_clock_constraint(self.tx_clk, 125e6)

        m.submodules.lane = lane = PCIeSERDESInterface(2)

        self.lane.frequency = int(125e6)

        # IF SOMETHING IS BROKE: Check if the TX actually transmits good data and not order-swapped data
        m.d.rxf += self.rx_clk.eq(~self.rx_clk)
        with m.If(~self.rx_clk):
            m.d.rxf += lane.rx_symbol[9:18].eq(serdes.lane.rx_symbol)
            m.d.rxf += lane.rx_valid[1].eq(serdes.lane.rx_valid)
        with m.Else():
            m.d.rxf += lane.rx_symbol[0:9].eq(serdes.lane.rx_symbol)
            m.d.rxf += lane.rx_valid[0].eq(serdes.lane.rx_valid)

            # To ensure that it outputs consistent data
            # m.d.rxf += self.lane.rx_symbol.eq(lane.rx_symbol)
            # m.d.rxf += self.lane.rx_valid.eq(lane.rx_valid)

        m.d.txf += self.tx_clk.eq(~self.tx_clk)
        # Do NOT add an invert here! It works, checked with x1 gearing. If you do, a "COM SKP SKP SKP" will turn into a "SKP COM SKP SKP"
        #with m.If(self.tx_clk):
        #    m.d.txf += serdes.lane.tx_symbol    .eq(lane.tx_symbol[9:18])
        #    m.d.txf += serdes.lane.tx_disp      .eq(lane.tx_disp[1])
        #    m.d.txf += serdes.lane.tx_set_disp  .eq(lane.tx_set_disp[1])
        #    m.d.txf += serdes.lane.tx_e_idle    .eq(lane.tx_e_idle[1])
        #with m.Else():
        #    m.d.txf += serdes.lane.tx_symbol    .eq(lane.tx_symbol[0:9])
        #    m.d.txf += serdes.lane.tx_disp      .eq(lane.tx_disp[0])
        #    m.d.txf += serdes.lane.tx_set_disp  .eq(lane.tx_set_disp[0])
        #    m.d.txf += serdes.lane.tx_e_idle    .eq(lane.tx_e_idle[0])

        # To ensure that it inputs consistent data
        # m.d.rxf += lane.tx_symbol.eq(self.lane.tx_symbol)
        # m.d.rxf += lane.tx_disp.eq(self.lane.tx_disp)
        # m.d.rxf += lane.tx_set_disp.eq(self.lane.tx_set_disp)
        # m.d.rxf += lane.tx_e_idle.eq(self.lane.tx_e_idle)

        m.d.txf += serdes.lane.tx_symbol.eq(
            Mux(self.tx_clk, lane.tx_symbol[9:18], lane.tx_symbol[0:9]))
        m.d.txf += serdes.lane.tx_disp.eq(
            Mux(self.tx_clk, lane.tx_disp[1], lane.tx_disp[0]))
        m.d.txf += serdes.lane.tx_set_disp.eq(
            Mux(self.tx_clk, lane.tx_set_disp[1], lane.tx_set_disp[0]))
        m.d.txf += serdes.lane.tx_e_idle.eq(
            Mux(self.tx_clk, lane.tx_e_idle[1], lane.tx_e_idle[0]))

        # CDC
        rx_fifo = m.submodules.rx_fifo = AsyncFIFOBuffered(width=20,
                                                           depth=4,
                                                           r_domain="rx",
                                                           w_domain="rxf")
        m.d.rxf += rx_fifo.w_data.eq(Cat(lane.rx_symbol, lane.rx_valid))
        m.d.comb += Cat(self.lane.rx_symbol,
                        self.lane.rx_valid).eq(rx_fifo.r_data)
        m.d.comb += rx_fifo.r_en.eq(1)
        m.d.rxf += rx_fifo.w_en.eq(self.rx_clk)

        tx_fifo = m.submodules.tx_fifo = AsyncFIFOBuffered(width=24,
                                                           depth=4,
                                                           r_domain="txf",
                                                           w_domain="tx")
        m.d.comb += tx_fifo.w_data.eq(
            Cat(self.lane.tx_symbol, self.lane.tx_set_disp, self.lane.tx_disp,
                self.lane.tx_e_idle))
        m.d.txf += Cat(lane.tx_symbol, lane.tx_set_disp, lane.tx_disp,
                       lane.tx_e_idle).eq(tx_fifo.r_data)
        m.d.txf += tx_fifo.r_en.eq(self.tx_clk)
        m.d.comb += tx_fifo.w_en.eq(1)
        #m.d.txf  += Cat(lane.tx_symbol, lane.tx_set_disp, lane.tx_disp, lane.tx_e_idle).eq(Cat(self.lane.tx_symbol, self.lane.tx_set_disp, self.lane.tx_disp, self.lane.tx_e_idle))

        serdes.lane.rx_invert = self.lane.rx_invert
        serdes.lane.rx_align = self.lane.rx_align
        serdes.lane.rx_aligned = self.lane.rx_aligned
        serdes.lane.rx_locked = self.lane.rx_locked
        serdes.lane.rx_present = self.lane.rx_present

        serdes.lane.det_enable = self.lane.det_enable
        serdes.lane.det_valid = self.lane.det_valid
        serdes.lane.det_status = self.lane.det_status
        serdes.slip = self.slip

        return m
Exemplo n.º 6
0
    def elaborate(self, platform: Platform) -> Module:
        m = Module()

        uart = self.uart
        words = self.words
        depth = self.depth
        data = self.data
        if (self.timeout >= 0):
            timer = Signal(range(self.timeout + 1), reset=self.timeout)
        word_sel = Signal(range(2 * words), reset=2 * words - 1)
        fifo = AsyncFIFOBuffered(width=8 * words,
                                 depth=depth,
                                 r_domain="sync",
                                 w_domain=self.data_domain)
        m.submodules += fifo

        m.d.comb += fifo.w_data.eq(data)

        def sendByteFSM(byte, nextState):
            sent = Signal(reset=0)
            with m.If(uart.tx.rdy):
                with m.If(sent == 0):
                    m.d.sync += uart.tx.data.eq(byte)
                    m.d.sync += uart.tx.ack.eq(1)
                    m.d.sync += sent.eq(1)
                with m.If(sent == 1):
                    m.d.sync += uart.tx.ack.eq(0)
                    m.d.sync += sent.eq(0)
                    m.next = nextState

        with m.FSM():
            with m.State("Collect"):
                with m.If(~fifo.w_rdy
                          | ((timer == 0) if self.timeout >= 0 else 0)):
                    m.d.comb += fifo.w_en.eq(0)
                    m.next = "Wait"
                with m.Else():
                    m.d.comb += fifo.w_en.eq(self.enable)
                    if self.timeout >= 0:
                        m.d.sync += timer.eq(timer - 1)

            with m.State("Wait"):
                m.d.sync += uart.rx.ack.eq(1)
                with m.If(uart.rx.rdy):
                    m.d.sync += uart.rx.ack.eq(0)
                    if self.timeout >= 0:
                        m.d.sync += timer.eq(self.timeout)
                    m.next = "Pre-Transmit"

            with m.State("Pre-Transmit"):
                sendByteFSM(ord('\n'), "Transmit-1")

            with m.State("Transmit-1"):
                with m.If(fifo.r_rdy):
                    m.d.sync += fifo.r_en.eq(1)
                    m.next = "Transmit-2"
                with m.Else():
                    m.next = "Collect"

            with m.State("Transmit-2"):
                m.d.sync += fifo.r_en.eq(0)
                m.next = "TransmitByte"

            with m.State("TransmitByte"):
                sent = Signal(reset=0)
                with m.If(uart.tx.rdy):
                    with m.If(sent == 0):
                        hexNumber = HexNumber(
                            fifo.r_data.word_select(word_sel, 4), Signal(8))
                        m.submodules += hexNumber
                        m.d.sync += uart.tx.data.eq(hexNumber.ascii)
                        m.d.sync += uart.tx.ack.eq(1)
                        m.d.sync += sent.eq(1)
                    with m.If(sent == 1):
                        m.d.sync += uart.tx.ack.eq(0)
                        m.d.sync += sent.eq(0)
                        with m.If(word_sel == 0):
                            m.d.sync += word_sel.eq(word_sel.reset)
                            m.next = "Separator"
                        with m.Else():
                            m.d.sync += word_sel.eq(word_sel - 1)
                with m.Else():
                    m.d.sync += uart.tx.ack.eq(0)

            with m.State("Separator"):
                sendByteFSM(ord('\n'), "Transmit-1")

            with m.State("TransmitEnd"):
                sendByteFSM(ord('Z'), "Collect")
        return m
Exemplo n.º 7
0
    def elaborate(self, platform):
        m = Module()
        m.submodules.ila = ila = self.ila

        if self._o_domain == self.domain:
            in_domain_stream = self.stream
        else:
            in_domain_stream = StreamInterface(
                payload_width=self.bits_per_sample)

        # Count where we are in the current transmission.
        current_sample_number = Signal(range(0, ila.sample_depth))

        # Always present the current sample number to our ILA, and the current
        # sample value to the UART.
        m.d.comb += [
            ila.captured_sample_number.eq(current_sample_number),
            in_domain_stream.payload.eq(ila.captured_sample)
        ]

        with m.FSM():

            # IDLE -- we're currently waiting for a trigger before capturing samples.
            with m.State("IDLE"):

                # Always allow triggering, as we're ready for the data.
                m.d.comb += self.ila.trigger.eq(self.trigger)

                # Once we're triggered, move onto the SAMPLING state.
                with m.If(self.trigger):
                    m.next = "SAMPLING"

            # SAMPLING -- the internal ILA is sampling; we're now waiting for it to
            # complete. This state is similar to IDLE; except we block triggers in order
            # to cleanly avoid a race condition.
            with m.State("SAMPLING"):

                # Once our ILA has finished sampling, prepare to read out our samples.
                with m.If(self.ila.complete):
                    m.d.sync += [
                        current_sample_number.eq(0),
                        in_domain_stream.first.eq(1)
                    ]
                    m.next = "SENDING"

            # SENDING -- we now have a valid buffer of samples to send up to the host;
            # we'll transmit them over our stream interface.
            with m.State("SENDING"):
                data_valid = Signal(reset=1)
                m.d.comb += [
                    # While we're sending, we're always providing valid data to the UART.
                    in_domain_stream.valid.eq(data_valid),

                    # Indicate when we're on the last sample.
                    in_domain_stream.last.eq(
                        current_sample_number == (self.sample_depth - 1))
                ]

                # Each time the UART accepts a valid word, move on to the next one.
                with m.If(in_domain_stream.ready):
                    with m.If(data_valid):
                        m.d.sync += [
                            current_sample_number.eq(current_sample_number +
                                                     1),
                            data_valid.eq(0),
                            in_domain_stream.first.eq(0)
                        ]

                        # If this was the last sample, we're done! Move back to idle.
                        with m.If(self.stream.last):
                            m.next = "IDLE"
                    with m.Else():
                        m.d.sync += data_valid.eq(1)

        # If we're not streaming out of the same domain we're capturing from,
        # we'll add some clock-domain crossing hardware.
        if self._o_domain != self.domain:
            in_domain_signals = Cat(in_domain_stream.first,
                                    in_domain_stream.payload,
                                    in_domain_stream.last)
            out_domain_signals = Cat(self.stream.first, self.stream.payload,
                                     self.stream.last)

            # Create our async FIFO...
            m.submodules.cdc = fifo = AsyncFIFOBuffered(
                width=len(in_domain_signals),
                depth=16,
                w_domain="sync",
                r_domain=self._o_domain)

            m.d.comb += [
                # ... fill it from our in-domain stream...
                fifo.w_data.eq(in_domain_signals),
                fifo.w_en.eq(in_domain_stream.valid),
                in_domain_stream.ready.eq(fifo.w_rdy),

                # ... and output it into our outupt stream.
                out_domain_signals.eq(fifo.r_data),
                self.stream.valid.eq(fifo.r_rdy),
                fifo.r_en.eq(self.stream.ready)
            ]

        # Convert our sync domain to the domain requested by the user, if necessary.
        if self.domain != "sync":
            m = DomainRenamer(self.domain)(m)

        return m