Exemplo n.º 1
0
Arquivo: jtag.py Projeto: zyp/luna
    def _synchronize_(self, m, output, o_domain="sync", stages=2):
        """ Creates a synchronized copy of this interface's I/O. """

        # Synchronize our inputs...
        m.submodules += [
            FFSynchronizer(self.sck, output.sck, o_domain=o_domain, stages=stages),
            FFSynchronizer(self.sdi, output.sdi, o_domain=o_domain, stages=stages),
            FFSynchronizer(self.cs,  output.cs,  o_domain=o_domain, stages=stages),
        ]

        # ... and connect our output directly through.
        m.d.comb += self.sdo.eq(output.sdo)
Exemplo n.º 2
0
    def elaborate(self, platform):
        m = Module()
        self.elaborate_read(m)
        self.elaborate_write(m)

        mem = Memory(width=16, depth=256)

        w_pointer = Signal()
        r_pointer = Signal()

        # Connect read and write sides to the memory
        # Addresses from r_addr, w_addr and pointers
        # NOTE: transparent=False is required or BRAM will not be inferred
        m.submodules.rp = rp = mem.read_port(domain=self.read_domain,
                                             transparent=False)
        m.d.comb += rp.addr.eq(Cat(self.read.addr, ~r_pointer))
        m.d.comb += self.read.data.eq(rp.data)

        m.submodules.wp = wp = mem.write_port(domain=self.write_domain)
        m.d.comb += wp.en.eq(self.write.en)
        m.d.comb += wp.addr.eq(Cat(self.write.addr, w_pointer))
        m.d.comb += wp.data.eq(self.write.data)

        # Handle read pointer toggle and send it cross domain to write pointer
        with m.If(self.read.toggle):
            m.d[self.read_domain] += r_pointer.eq(~r_pointer)
        m.submodules.pointer = FFSynchronizer(r_pointer,
                                              w_pointer,
                                              o_domain=self.write_domain,
                                              stages=3)
        last_w_pointer = Signal()
        m.d[self.write_domain] += last_w_pointer.eq(w_pointer)
        m.d.comb += self.write.ready.eq(w_pointer != last_w_pointer)

        return m
Exemplo n.º 3
0
    def elaborate(self, platform):
        m = Module()

        memory = m.submodules.memory = self.memory

        address_stream = PacketizedStream(bits_for(self.max_packet_size))
        with m.If(~self.done):
            m.d.comb += address_stream.valid.eq(1)
            m.d.comb += address_stream.last.eq(self.read_ptr == self.packet_length)
            m.d.comb += address_stream.payload.eq(self.read_ptr)
            with m.If(address_stream.ready):
                m.d.sync += self.read_ptr.eq(self.read_ptr + 1)
                m.d.sync += self.done.eq(self.read_ptr == self.packet_length)

        reset = Signal()
        m.submodules += FFSynchronizer(self.reset, reset)
        with m.If(Changed(m, reset)):
            m.d.sync += self.read_ptr.eq(0)
            m.d.sync += self.done.eq(0)

        reader = m.submodules.reader = StreamMemoryReader(address_stream, memory)
        buffer = m.submodules.buffer = StreamBuffer(reader.output)
        m.d.comb += self.output.connect_upstream(buffer.output)

        return m
    def elaborate(self, platform):
        m = Module()
        board_spi = platform.request("debug_spi")

        # Create a set of registers, and expose them over SPI.
        spi_registers = SPIRegisterInterface(
            default_read_value=0x4C554E41)  #default read = u'LUNA'
        m.submodules.spi_registers = spi_registers

        # Fill in some example registers.
        # (Register 0 is reserved for size autonegotiation).
        spi_registers.add_read_only_register(1, read=0xc001cafe)
        led_reg = spi_registers.add_register(2, size=6, name="leds")
        spi_registers.add_read_only_register(3, read=0xdeadbeef)

        # ... and tie our LED register to our LEDs.
        led_out = Cat(
            [platform.request("led", i, dir="o") for i in range(0, 6)])
        m.d.comb += led_out.eq(led_reg)

        #
        # Structural connections.
        #
        sck = Signal()
        sdi = Signal()
        sdo = Signal()
        cs = Signal()

        #
        # Synchronize each of our I/O SPI signals, where necessary.
        #
        m.submodules += FFSynchronizer(board_spi.sck, sck)
        m.submodules += FFSynchronizer(board_spi.sdi, sdi)
        m.submodules += FFSynchronizer(board_spi.cs, cs)
        m.d.comb += board_spi.sdo.eq(sdo)

        # Connect our register interface to our board SPI.
        m.d.comb += [
            spi_registers.sck.eq(sck),
            spi_registers.sdi.eq(sdi),
            sdo.eq(spi_registers.sdo),
            spi_registers.cs.eq(cs)
        ]

        return m
Exemplo n.º 5
0
    def elaborate(self, platform):
        m = Module()

        if self.has_tx:
            m.d.comb += self.tx_t.oe.eq(1)
            if self.invert_tx:
                m.d.comb += self.tx_t.o.eq(~self.tx_o)
            else:
                m.d.comb += self.tx_t.o.eq(self.tx_o)

        if self.has_rx:
            if self.invert_rx:
                m.submodules += FFSynchronizer(~self.rx_t.i,
                                               self.rx_i,
                                               reset=1)
            else:
                m.submodules += FFSynchronizer(self.rx_t.i, self.rx_i, reset=1)

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

        # Keep track of how many cycles we'll keep our PHY in reset.
        # This is larger than any requirement, in order to work with a broad swathe of PHYs,
        # in case a PHY other than the TUSB1310A ever makes it to the market.
        cycles_in_reset = int(5e-6 * 50e6)
        cycles_left_in_reset = Signal(range(cycles_in_reset),
                                      reset=cycles_in_reset - 1)

        # Create versions of our phy_status signals that are observable:
        # 1) as an asynchronous inputs for startup pulses
        # 2) as a single-cycle pulse, for power-state-change notifications
        phy_status = Signal()

        # Convert our PHY status signal into a simple, sync-domain signal.
        m.submodules += FFSynchronizer(self.phy_status[0] | self.phy_status[1],
                                       phy_status),

        with m.FSM():

            # STARTUP_RESET -- post configuration, we'll reset the PIPE PHY.
            # This is distinct from the PHY's built-in power-on-reset, as we run this
            # on every FPGA configuration.
            with m.State("STARTUP_RESET"):
                m.d.comb += [
                    self.reset.eq(1),
                ]

                # Once we've extended past a reset time, we can move on.
                m.d.sync += cycles_left_in_reset.eq(cycles_left_in_reset - 1)
                with m.If(cycles_left_in_reset == 0):
                    m.next = "DETECT_PHY_STARTUP"

            # DETECT_PHY_STARTUP -- post-reset, the PHY should drive its status line high.
            # We'll wait for this to happen, so we can track the PHY's progress.
            with m.State("DETECT_PHY_STARTUP"):

                with m.If(phy_status):
                    m.next = "WAIT_FOR_STARTUP"

            # WAIT_FOR_STARTUP -- we've now detected that the PHY is starting up.
            # We'll wait for that startup signal to be de-asserted, indicating that the PHY is ready.
            with m.State("WAIT_FOR_STARTUP"):

                # For now, we'll start up in P0. This will change once we implement proper RxDetect.
                with m.If(~phy_status):
                    m.next = "READY"

            # READY -- our PHY is all started up and ready for use.
            # For now, we'll remain here until we're reset.
            with m.State("READY"):
                m.d.comb += self.ready.eq(1)

        return m
Exemplo n.º 7
0
    def elaborate(self, platform):
        m = Module()

        ddr_reset = Signal()
        m.submodules += FFSynchronizer(self.reset,
                                       ddr_reset,
                                       o_domain=self.ddr_domain)

        m.d.comb += self.pins.o_clk.eq(ClockSignal(self.ddr_domain))
        m.d.comb += self.pins.oe.eq(~self.reset)

        hs_fifo = m.submodules.hs_fifo = BufferedAsyncStreamFIFO(
            self.input, 8, o_domain=self.ddr_domain)
        hs_payload = Signal(8)

        was_valid = Signal()
        m.submodules += FFSynchronizer(~was_valid, self.is_idle)

        with m.FSM(domain=self.ddr_domain):
            for i in range(4):
                with m.State(f"{i}"):
                    if i == 3:
                        with m.If(hs_fifo.output.valid):
                            m.d[self.ddr_domain] += hs_payload.eq(
                                hs_fifo.output.payload)
                            m.d[self.ddr_domain] += self.idle.eq(
                                Repl(hs_fifo.output.payload[7], 8))
                            m.d[self.ddr_domain] += was_valid.eq(1)
                        with m.Else():
                            m.d[self.ddr_domain] += hs_payload.eq(self.idle)
                            m.d[self.ddr_domain] += was_valid.eq(0)
                        m.d.comb += hs_fifo.output.ready.eq(was_valid)
                    m.d.comb += self.pins.o0.eq(
                        fake_differential(hs_payload[i * 2 + 0]))
                    m.d.comb += self.pins.o1.eq(
                        fake_differential(hs_payload[i * 2 + 1]))
                    m.next = f"{(i + 1) % 4}"

        return ResetInserter({
            self.ddr_domain: ddr_reset,
            "sync": self.reset
        })(m)
Exemplo n.º 8
0
 def elaborate(self, platform):
     m = Module()
     pads = self._pads
     m.d.comb += [
         pads.sbwtck_t.oe.eq(1),
         pads.sbwtck_t.o.eq(self.sbwtck),
         pads.sbwtdio_t.oe.eq(~self.sbwtd_z),
         pads.sbwtdio_t.o.eq(self.sbwtd_o),
     ]
     m.submodules += [
         FFSynchronizer(pads.sbwtdio_t.i, self.sbwtd_i),
     ]
     return m
Exemplo n.º 9
0
 def elaborate(self, platform):
     m = Module()
     pads = self._pads
     m.submodules += [
         FFSynchronizer(pads.tck_t.i, self.tck),
         FFSynchronizer(pads.tms_t.i, self.tms),
         FFSynchronizer(pads.tdi_t.i, self.tdi),
         FFSynchronizer(pads.tdo_t.i, self.tdo),
         FFSynchronizer(pads.srst_t.i, self.srst),
     ]
     m.d.comb += [
         pads.tck_t.oe.eq(0),
         pads.tms_t.oe.eq(0),
         pads.tdi_t.oe.eq(0),
         pads.tdo_t.oe.eq(0),
         pads.srst_t.oe.eq(0),
         self.states.tck.eq(self.tck),
         self.states.tms.eq(self.tms),
         self.states.tdi.eq(self.tdi),
         self.states.tdo.eq(self.tdo),
         self.states.srst.eq(self.srst),
     ]
     return m
Exemplo n.º 10
0
    def elaborate(self, platform):
        m = Module()
        board_spi = platform.request("debug_spi")

        # Use our command interface.
        m.submodules.interface = self.interface

        sck = Signal()
        sdi = Signal()
        sdo = Signal()
        cs  = Signal()

        #
        # Synchronize each of our I/O SPI signals, where necessary.
        #
        m.submodules += FFSynchronizer(board_spi.sck, sck)
        m.submodules += FFSynchronizer(board_spi.sdi, sdi)
        m.submodules += FFSynchronizer(board_spi.cs,  cs)
        m.d.comb     += board_spi.sdo.eq(sdo)

        # Connect our command interface to our board SPI.
        m.d.comb += [
            self.interface.sck.eq(sck),
            self.interface.sdi.eq(sdi),
            sdo.eq(self.interface.sdo),
            self.interface.cs .eq(cs)
        ]

        # Turn on a single LED, just to show something's running.
        led = platform.request('led', 0)
        m.d.comb += led.eq(1)

        # Echo back the last received data.
        m.d.comb += self.interface.word_out.eq(self.interface.word_in)

        return m
Exemplo n.º 11
0
 def elaborate(self, platform):
     m = Module()
     m.d.comb += [
         self.pads.clk_t.oe.eq(1),
         self.pads.clk_t.o.eq(self.clk),
     ]
     m.submodules += [
         FFSynchronizer(self.pads.din_t.i, self.din),
     ]
     if hasattr(self.pads, "osc_t"):
         m.d.comb += [
             self.pads.osc_t.oe.eq(1),
             self.pads.osc_t.o.eq(self.osc),
         ]
     return m
Exemplo n.º 12
0
    def elaborate(self, platform):
        m = Module()
        m.submodules += self.analyzer

        pins_i = Signal.like(self.pads.i_t.i)
        pins_r = Signal.like(self.pads.i_t.i)
        m.submodules += FFSynchronizer(self.pads.i_t.i, pins_i)

        m.d.sync += pins_r.eq(pins_i)
        m.d.comb += [
            self.event_source.data.eq(pins_i),
            self.event_source.trigger.eq(pins_i != pins_r)
        ]

        return m
Exemplo n.º 13
0
    def elaborate(self, platform):
        m = Module()

        m.d.comb += [
            self.pads.clock_t.o.eq(0),
            self.pads.clock_t.oe.eq(~self.clock_o),
            self.pads.data_t.o.eq(0),
            self.pads.data_t.oe.eq(~self.data_o),
        ]
        m.submodules += [
            FFSynchronizer(self.pads.clock_t.i, self.clock_i, reset=1),
            FFSynchronizer(self.pads.data_t.i, self.data_i, reset=1),
        ]

        clock_s = Signal(reset=1)
        clock_r = Signal(reset=1)
        m.d.sync += [
            clock_s.eq(self.clock_i),
            clock_r.eq(clock_s),
            self.falling.eq(clock_r & ~clock_s),
            self.rising.eq(~clock_r & clock_s),
        ]

        return m
Exemplo n.º 14
0
    def elaborate(self, platform):
        m = Module()

        timer = Signal.like(self.divisor)
        shreg = Record(_wire_layout(len(self.data), self._parity))
        bitno = Signal(range(len(shreg)))

        if self._pins is not None:
            m.submodules += FFSynchronizer(self._pins.rx.i, self.i, reset=1)

        with m.FSM() as fsm:
            with m.State("IDLE"):
                with m.If(~self.i):
                    m.d.sync += [
                        bitno.eq(len(shreg) - 1),
                        timer.eq(self.divisor >> 1),
                    ]
                    m.next = "BUSY"

            with m.State("BUSY"):
                with m.If(timer != 0):
                    m.d.sync += timer.eq(timer - 1)
                with m.Else():
                    m.d.sync += [
                        shreg.eq(Cat(shreg[1:], self.i)),
                        bitno.eq(bitno - 1),
                        timer.eq(self.divisor - 1),
                    ]
                    with m.If(bitno == 0):
                        m.next = "DONE"

            with m.State("DONE"):
                with m.If(self.ack):
                    m.d.sync += [
                        self.data.eq(shreg.data),
                        self.err.frame.eq(~(
                            (shreg.start == 0) & (shreg.stop == 1))),
                        self.err.parity.eq(
                            ~(shreg.parity == _compute_parity_bit(
                                shreg.data, self._parity))),
                    ]
                m.d.sync += self.err.overflow.eq(~self.ack)
                m.next = "IDLE"

        with m.If(self.ack):
            m.d.sync += self.rdy.eq(fsm.ongoing("DONE"))

        return m
Exemplo n.º 15
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
        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=24, depth=10, 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.º 16
0
    def elaborate(self, platform):
        m = Module()

        update = Signal()
        freeze = Signal()

        # DDRDLLA instance -------------------------------------------------------------------------
        _lock = Signal()
        lock = Signal()
        lock_d = Signal()
        m.submodules += Instance("DDRDLLA",
                                 i_CLK=ClockSignal("sync2x"),
                                 i_RST=ResetSignal("init"),
                                 i_UDDCNTLN=~update,
                                 i_FREEZE=freeze,
                                 o_DDRDEL=self.delay,
                                 o_LOCK=_lock)
        m.submodules += FFSynchronizer(_lock, lock, o_domain="init")
        m.d.init += lock_d.eq(lock)

        # DDRDLLA/DDQBUFM/ECLK initialization sequence ---------------------------------------------
        t = 8  # in cycles
        tl = Timeline([
            (1 * t, [freeze.eq(1)]),  # Freeze DDRDLLA
            (2 * t, [self.stop.eq(1)]),  # Stop ECLK domain
            (3 * t, [self.reset.eq(1)]),  # Reset ECLK domain
            (4 * t, [self.reset.eq(0)]),  # Release ECLK domain reset
            (5 * t, [self.stop.eq(0)]),  # Release ECLK domain stop
            (6 * t, [freeze.eq(0)]),  # Release DDRDLLA freeze
            (7 * t, [self.pause.eq(1)]),  # Pause DQSBUFM
            (8 * t, [update.eq(1)]),  # Update DDRDLLA
            (9 * t, [update.eq(0)]),  # Release DDRDMMA update
            (10 * t, [self.pause.eq(0)]),  # Release DQSBUFM pause
        ])
        m.d.comb += tl.trigger.eq(
            lock & ~lock_d)  # Trigger timeline on lock rising edge
        m.submodules += DomainRenamer("init")(tl)

        return m
Exemplo n.º 17
0
    def elaborate(self, platform):
        m = Module()

        cart = []
        # synchronize the quite asynchronous SNES bus signals to our domain.
        # this seems to work okay, but the main sd2snes uses more sophisticated
        # techniques to determine the bus state and we may have to steal some.
        for fi, field in enumerate(self.cart_signals._fields):
            cart_signal = self.cart_signals[fi]
            sync_signal = Signal(len(cart_signal), name=field)
            m.submodules["ffsync_"+field] = \
                FFSynchronizer(cart_signal, sync_signal)
            cart.append(sync_signal)
        cart = self.cart_signals._make(cart)

        # keep track of the SNES clock cycle so the system can time events
        cycle_counter = Signal(32)
        last_clock = Signal()
        m.d.sync += last_clock.eq(cart.clock)
        with m.If(~last_clock & cart.clock):
            m.d.sync += cycle_counter.eq(cycle_counter + 1)
        m.d.comb += self.o_cycle_count.eq(cycle_counter)

        # for now we just need to monitor reads, so we look for when the read
        # line is asserted. we assume the address is valid at that point but tbh
        # we're not 100% sure.
        last_rd = Signal()
        snes_read_started = Signal()
        m.d.sync += last_rd.eq(cart.rd)
        m.d.comb += snes_read_started.eq(last_rd & ~cart.rd)

        m.d.comb += [
            self.o_valid.eq(snes_read_started),
            self.o_addr.eq(cart.addr),
            self.o_data.eq(cart.data),
        ]

        return m
Exemplo n.º 18
0
    def elaborate(self, platform: BetaPlatform):
        m = Module()

        m.submodules += self.frame_req

        platform.ps7.fck_domain(requested_frequency=100e6)

        sensor = platform.request("sensor")
        platform.ps7.fck_domain(250e6, "sensor_clk")
        m.d.comb += sensor.lvds_clk.eq(ClockSignal("sensor_clk"))
        m.d.comb += sensor.reset.eq(self.sensor_reset)

        m.d.comb += [
            sensor.frame_req.eq(self.frame_req.pulse),
            sensor.t_exp1.eq(0),
            sensor.t_exp2.eq(0),
        ]

        m.submodules.sensor_spi = Cmv12kSpi(platform.request("sensor_spi"))
        sensor_rx = m.submodules.sensor_rx = Cmv12kRx(sensor)
        stats = m.submodules.stats = DomainRenamer("cmv12k_hword")(Stats())

        m.d.comb += [
            stats.ctrl_lane.eq(sensor_rx.phy.output[-1]),
            stats.ctrl_valid.eq(sensor_rx.phy.output_valid),
        ]

        add_ila(platform, trace_length=2048, domain="cmv12k_hword", after_trigger=2048-768)
        probe(m, sensor_rx.phy.output_valid, name="output_valid")
        for lane in range(32):
           probe(m, sensor_rx.phy.output[lane], name=f"lane_{lane:02d}")
        probe(m, sensor_rx.phy.output[-1], name="lane_ctrl")

        capture_pattern_end = Signal()
        m.submodules += FFSynchronizer(self.capture_pattern_end, capture_pattern_end)
        trigger(m, Mux(capture_pattern_end, stats.frame_end_trigger, stats.frame_start_trigger))

        return m
Exemplo n.º 19
0
    def elaborate(self, platform):
        m = Module()

        memory = m.submodules.memory = self.memory
        write_port = m.submodules.write_port = memory.write_port(domain="sync")
        with m.If(~self.packet_done & (self.write_pointer < self.max_packet_size)):
            m.d.comb += self.input.ready.eq(1)
            with m.If(self.input.valid):
                m.d.comb += write_port.en.eq(1)
                m.d.comb += write_port.addr.eq(self.write_pointer)
                m.d.comb += write_port.data.eq(self.input.payload)
                m.d.sync += self.write_pointer.eq(self.write_pointer + 1)
                with m.If(self.input.last):
                    m.d.sync += self.packet_done.eq(1)

        reset = Signal()
        m.submodules += FFSynchronizer(self.reset, reset)
        with m.If(Changed(m, reset)):
            m.d.sync += self.write_pointer.eq(0)
            m.d.sync += self.packet_done.eq(0)


        return m
Exemplo n.º 20
0
    def elaborate(self, platform):
        m = Module()

        if hasattr(platform, "ila_error"):
            raise platform.ila_error

        after_trigger = Signal.like(self.after_trigger)
        m.submodules += FFSynchronizer(self.after_trigger, after_trigger)

        assert hasattr(platform, "trigger"), "No trigger in Design"
        trigger = Signal()
        m.submodules += FFSynchronizer(platform.trigger, trigger)
        assert hasattr(platform, "probes"), "No probes in Design"
        platform_probes = list(platform.probes.items())
        probes = [(k, Signal.like(signal))
                  for k, (signal, decoder) in platform_probes]
        for (_, (i, _)), (_, o) in zip(platform_probes, probes):
            m.submodules += FFSynchronizer(i, o)
        self.probes = [(k, (len(signal), decoder))
                       for k, (signal, decoder) in platform_probes]

        probe_bits = sum(length for name, (length, decoder) in self.probes)
        print(f"ila: using {probe_bits} probe bits")
        self.mem = m.submodules.mem = SocMemory(
            width=ceil(probe_bits / 32) * 32,
            depth=self.trace_length,
            soc_write=False,
            attrs=dict(syn_ramstyle="block_ram"))
        write_port = m.submodules.write_port = self.mem.write_port(
            domain="sync")

        since_reset = Signal(range(self.trace_length + 1))
        with m.If(self.running):
            with m.If(self.write_ptr < (self.trace_length - 1)):
                m.d.sync += self.write_ptr.eq(self.write_ptr + 1)
            with m.Else():
                m.d.sync += self.write_ptr.eq(0)
            m.d.comb += write_port.addr.eq(self.write_ptr)
            m.d.comb += write_port.en.eq(1)
            m.d.comb += write_port.data.eq(Cat([s for _, s in probes]))

            # we wait trace_length cycles to be sure to overwrite the whole buffer at least once
            # and avoid confusing results
            with m.If(since_reset < self.trace_length):
                m.d.sync += since_reset.eq(since_reset + 1)

            with m.If(self.trigger_since == 0):
                with m.If(trigger & (since_reset > self.trace_length - 1)):
                    m.d.sync += self.trigger_since.eq(1)
            with m.Else():
                with m.If(self.trigger_since < (after_trigger - 1)):
                    m.d.sync += self.trigger_since.eq(self.trigger_since + 1)
                with m.Else():
                    m.d.sync += self.running.eq(0)
                    m.d.sync += self.initial_.eq(0)
        with m.Else():
            reset = Signal()
            m.submodules += FFSynchronizer(self.reset, reset)
            with m.If(Changed(m, reset)):
                m.d.sync += self.running.eq(1)
                m.d.sync += self.trigger_since.eq(0)
                m.d.sync += self.write_ptr.eq(0)
                m.d.sync += since_reset.eq(0)

        return m
Exemplo n.º 21
0
    def elaborate(self, platform):
        m = Module()
        if self.depth == 0:
            m.d.comb += [
                self.w_rdy.eq(0),
                self.r_rdy.eq(0),
            ]
            return m

        # The design of this queue is the "style #2" from Clifford E. Cummings' paper "Simulation
        # and Synthesis Techniques for Asynchronous FIFO Design":
        # http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf

        do_write = self.w_rdy & self.w_en
        do_read = self.r_rdy & self.r_en

        # TODO: extract this pattern into lib.cdc.GrayCounter
        produce_w_bin = Signal(self._ctr_bits)
        produce_w_nxt = Signal(self._ctr_bits)
        m.d.comb += produce_w_nxt.eq(produce_w_bin + do_write)
        m.d[self._w_domain] += produce_w_bin.eq(produce_w_nxt)

        # Note: Both read-domain counters must be reset_less (see comments below)
        consume_r_bin = Signal(self._ctr_bits, reset_less=True)
        consume_r_nxt = Signal(self._ctr_bits)
        m.d.comb += consume_r_nxt.eq(consume_r_bin + do_read)
        m.d[self._r_domain] += consume_r_bin.eq(consume_r_nxt)

        produce_w_gry = Signal(self._ctr_bits)
        produce_r_gry = Signal(self._ctr_bits)
        produce_enc = m.submodules.produce_enc = \
            GrayEncoder(self._ctr_bits)
        produce_cdc = m.submodules.produce_cdc = \
            FFSynchronizer(produce_w_gry, produce_r_gry, o_domain=self._r_domain)
        m.d.comb += produce_enc.i.eq(produce_w_nxt),
        m.d[self._w_domain] += produce_w_gry.eq(produce_enc.o)

        consume_r_gry = Signal(self._ctr_bits, reset_less=True)
        consume_w_gry = Signal(self._ctr_bits)
        consume_enc = m.submodules.consume_enc = \
            GrayEncoder(self._ctr_bits)
        consume_cdc = m.submodules.consume_cdc = \
            FFSynchronizer(consume_r_gry, consume_w_gry, o_domain=self._w_domain)
        m.d.comb += consume_enc.i.eq(consume_r_nxt)
        m.d[self._r_domain] += consume_r_gry.eq(consume_enc.o)

        consume_w_bin = Signal(self._ctr_bits)
        consume_dec = m.submodules.consume_dec = \
            GrayDecoder(self._ctr_bits)
        m.d.comb += consume_dec.i.eq(consume_w_gry),
        m.d[self._w_domain] += consume_w_bin.eq(consume_dec.o)

        produce_r_bin = Signal(self._ctr_bits)
        produce_dec = m.submodules.produce_dec = \
            GrayDecoder(self._ctr_bits)
        m.d.comb += produce_dec.i.eq(produce_r_gry),
        m.d.comb += produce_r_bin.eq(produce_dec.o)

        w_full = Signal()
        r_empty = Signal()
        m.d.comb += [
            w_full.eq((produce_w_gry[-1] != consume_w_gry[-1])
                      & (produce_w_gry[-2] != consume_w_gry[-2])
                      & (produce_w_gry[:-2] == consume_w_gry[:-2])),
            r_empty.eq(consume_r_gry == produce_r_gry),
        ]

        m.d[self._w_domain] += self.w_level.eq((produce_w_bin - consume_w_bin))
        m.d.comb += self.r_level.eq((produce_r_bin - consume_r_bin))

        storage = Memory(width=self.width, depth=self.depth)
        w_port = m.submodules.w_port = storage.write_port(
            domain=self._w_domain)
        r_port = m.submodules.r_port = storage.read_port(domain=self._r_domain,
                                                         transparent=False)
        m.d.comb += [
            w_port.addr.eq(produce_w_bin[:-1]),
            w_port.data.eq(self.w_data),
            w_port.en.eq(do_write),
            self.w_rdy.eq(~w_full),
        ]
        m.d.comb += [
            r_port.addr.eq(consume_r_nxt[:-1]),
            self.r_data.eq(r_port.data),
            r_port.en.eq(1),
            self.r_rdy.eq(~r_empty),
        ]

        # Reset handling to maintain FIFO and CDC invariants in the presence of a write-domain
        # reset.
        # There is a CDC hazard associated with resetting an async FIFO - Gray code counters which
        # are reset to 0 violate their Gray code invariant. One way to handle this is to ensure
        # that both sides of the FIFO are asynchronously reset by the same signal. We adopt a
        # slight variation on this approach - reset control rests entirely with the write domain.
        # The write domain's reset signal is used to asynchronously reset the read domain's
        # counters and force the FIFO to be empty when the write domain's reset is asserted.
        # This requires the two read domain counters to be marked as "reset_less", as they are
        # reset through another mechanism. See https://github.com/nmigen/nmigen/issues/181 for the
        # full discussion.
        w_rst = ResetSignal(domain=self._w_domain, allow_reset_less=True)
        r_rst = Signal()

        # Async-set-sync-release synchronizer avoids CDC hazards
        rst_cdc = m.submodules.rst_cdc = \
            AsyncFFSynchronizer(w_rst, r_rst, o_domain=self._r_domain)

        # Decode Gray code counter synchronized from write domain to overwrite binary
        # counter in read domain.
        rst_dec = m.submodules.rst_dec = \
            GrayDecoder(self._ctr_bits)
        m.d.comb += rst_dec.i.eq(produce_r_gry)
        with m.If(r_rst):
            m.d.comb += r_empty.eq(1)
            m.d[self._r_domain] += consume_r_gry.eq(produce_r_gry)
            m.d[self._r_domain] += consume_r_bin.eq(rst_dec.o)
            m.d[self._r_domain] += self.r_rst.eq(1)
        with m.Else():
            m.d[self._r_domain] += self.r_rst.eq(0)

        if platform == "formal":
            with m.If(Initial()):
                m.d.comb += Assume(produce_w_gry == (produce_w_bin
                                                     ^ produce_w_bin[1:]))
                m.d.comb += Assume(consume_r_gry == (consume_r_bin
                                                     ^ consume_r_bin[1:]))

        return m
Exemplo n.º 22
0
    def elaborate(self, platform):
        m = Module()

        #
        # Reference clock selection.
        #

        # If we seem to have a raw pin record, we'll assume we're being passed the external REFCLK.
        # We'll instantiate an instance that captures the reference clock signal.
        if hasattr(self._refclk, 'p'):
            refclk = Signal()
            m.submodules.refclk_input = refclk_in = Instance(
                "EXTREFB",
                i_REFCLKP=self._refclk.p,
                i_REFCLKN=self._refclk.n,
                o_REFCLKO=refclk,
                p_REFCK_PWDNB="0b1",
                p_REFCK_RTERM="0b1",  # 100 Ohm
            )
            refclk_in.attrs["LOC"] = f"EXTREF{self._refclk_num}"

        # Otherwise, we'll accept the reference clock directly.
        else:
            refclk = self._refclk

        #
        # Raw serdes.
        #
        pll_config = ECP5SerDesPLLConfiguration(
            refclk, refclk_freq=self._refclk_frequency, linerate=5e9)
        serdes = ECP5SerDes(
            pll_config=pll_config,
            tx_pads=self._tx_pads,
            rx_pads=self._rx_pads,
            channel=self._channel,
        )
        m.submodules.serdes = serdes
        m.d.comb += [
            serdes.train_equalizer.eq(self.train_equalizer),
            self.ready.eq(serdes.tx_ready & serdes.rx_ready)
        ]

        #
        # Transmit datapath.
        #
        m.submodules.tx_datapath = tx_datapath = TransmitPreprocessing()
        m.d.comb += [
            serdes.tx_idle.eq(self.tx_idle),
            serdes.tx_enable.eq(self.enable),
            tx_datapath.sink.stream_eq(self.sink),
            serdes.sink.stream_eq(tx_datapath.source),
            serdes.tx_gpio_en.eq(self.use_tx_as_gpio),
            serdes.tx_gpio.eq(self.tx_gpio)
        ]

        #
        # Receive datapath.
        #
        m.submodules.rx_datapath = rx_datapath = ReceivePostprocessing()
        m.d.comb += [
            self.rx_idle.eq(serdes.rx_idle),
            serdes.rx_enable.eq(self.enable),
            serdes.rx_align.eq(self.rx_align),
            rx_datapath.align.eq(self.rx_align),
            rx_datapath.sink.stream_eq(serdes.source),
            self.source.stream_eq(rx_datapath.source)
        ]

        # Pass through a synchronized version of our SerDes' rx-gpio.
        m.submodules += FFSynchronizer(serdes.rx_gpio,
                                       self.rx_gpio,
                                       o_domain="fast")

        #
        # LFPS Detection
        #
        m.submodules.lfps_detector = lfps_detector = LFPSSquareWaveDetector(
            self._fast_clock_frequency)
        m.d.comb += [
            lfps_detector.rx_gpio.eq(self.rx_gpio),
            self.lfps_signaling_detected.eq(lfps_detector.present)
        ]

        # debug signals
        m.d.comb += [
            self.raw_rx_data.eq(serdes.source.data),
            self.raw_rx_ctrl.eq(serdes.source.ctrl),
        ]

        return m
Exemplo n.º 23
0
    def elaborate(self, platform):
        m = Module()

        # The ECP5 SerDes uses a simple feedback mechanism to keep its FIFO clocks in sync
        # with the FPGA's fabric. Accordingly, we'll need to capture the output clocks and then
        # pass them back to the SerDes; this allows the placer to handle clocking correctly, allows us
        # to attach clock constraints for analysis, and allows us to use these clocks for -very- simple tasks.
        txoutclk = Signal()
        rxoutclk = Signal()

        # Internal state.
        rx_los = Signal()
        rx_lol = Signal()
        rx_lsm = Signal()
        rx_align = Signal()
        rx_bus = Signal(24)

        tx_lol = Signal()
        tx_bus = Signal(24)

        #
        # Clock domain crossing.
        #
        tx_produce_square_wave = Signal()
        tx_produce_pattern = Signal()
        tx_pattern = Signal(20)

        m.submodules += [
            # Transmit control  synchronization.
            FFSynchronizer(self.tx_produce_square_wave,
                           tx_produce_square_wave,
                           o_domain="tx"),
            FFSynchronizer(self.tx_produce_pattern,
                           tx_produce_pattern,
                           o_domain="tx"),
            FFSynchronizer(self.tx_pattern, tx_pattern, o_domain="tx"),

            # Receive control synchronization.
            FFSynchronizer(self.rx_align, rx_align, o_domain="rx"),
            FFSynchronizer(rx_los, self.rx_idle, o_domain="sync"),
        ]

        #
        # Clocking / reset control.
        #

        # The SerDes needs to be brought up gradually; we'll do that here.
        m.submodules.reset_sequencer = reset = ECP5ResetSequencer()
        m.d.comb += [
            reset.tx_pll_locked.eq(~tx_lol),
            reset.rx_pll_locked.eq(~rx_lol)
        ]

        # Create a local transmit domain, for our transmit-side hardware.
        m.domains.tx = ClockDomain()
        m.d.comb += ClockSignal("tx").eq(txoutclk)
        m.submodules += [
            ResetSynchronizer(ResetSignal("sync"), domain="tx"),
            FFSynchronizer(~ResetSignal("tx"), self.tx_ready)
        ]

        # Create the same setup, buf for the receive side.
        m.domains.rx = ClockDomain()
        m.d.comb += ClockSignal("rx").eq(rxoutclk)
        m.submodules += [
            ResetSynchronizer(ResetSignal("sync"), domain="rx"),
            FFSynchronizer(~ResetSignal("rx"), self.rx_ready)
        ]

        #
        # Core SerDes instantiation.
        #
        serdes_params = dict(
            # DCU — power management
            p_D_MACROPDB="0b1",
            p_D_IB_PWDNB="0b1",  # undocumented (required for RX)
            p_D_TXPLL_PWDNB="0b1",
            i_D_FFC_MACROPDB=1,

            # DCU — reset
            i_D_FFC_MACRO_RST=ResetSignal("sync"),
            i_D_FFC_DUAL_RST=ResetSignal("sync"),

            # DCU — clocking
            i_D_REFCLKI=self._pll.refclk,
            o_D_FFS_PLOL=tx_lol,
            p_D_REFCK_MODE={
                25: "0b100",
                20: "0b000",
                16: "0b010",
                10: "0b001",
                8: "0b011"
            }[self._pll.config["mult"]],
            p_D_TX_MAX_RATE="5.0",  # 5.0 Gbps
            p_D_TX_VCO_CK_DIV={
                32: "0b111",
                16: "0b110",
                8: "0b101",
                4: "0b100",
                2: "0b010",
                1: "0b000"
            }[1],  # DIV/1
            p_D_BITCLK_LOCAL_EN="0b1",  # Use clock from local PLL

            # Clock multiplier unit configuration
            p_D_CMUSETBIASI=
            "0b00",  # begin undocumented (10BSER sample code used)
            p_D_CMUSETI4CPP="0d3",
            p_D_CMUSETI4CPZ="0d3",
            p_D_CMUSETI4VCO="0b00",
            p_D_CMUSETICP4P="0b01",
            p_D_CMUSETICP4Z="0b101",
            p_D_CMUSETINITVCT="0b00",
            p_D_CMUSETISCL4VCO="0b000",
            p_D_CMUSETP1GM="0b000",
            p_D_CMUSETP2AGM="0b000",
            p_D_CMUSETZGM="0b000",
            p_D_SETIRPOLY_AUX="0b01",
            p_D_SETICONST_AUX="0b01",
            p_D_SETIRPOLY_CH="0b01",
            p_D_SETICONST_CH="0b10",
            p_D_SETPLLRC="0d1",
            p_D_RG_EN="0b0",
            p_D_RG_SET="0b00",
            p_D_REQ_ISET="0b011",
            p_D_PD_ISET="0b11",  # end undocumented

            # DCU — FIFOs
            p_D_LOW_MARK=
            "0d4",  # Clock compensation FIFO low  water mark (mean=8)
            p_D_HIGH_MARK=
            "0d12",  # Clock compensation FIFO high water mark (mean=8)

            # CHX common ---------------------------------------------------------------------------
            # CHX — protocol
            p_CHX_PROTOCOL="10BSER",
            p_CHX_UC_MODE="0b1",
            p_CHX_ENC_BYPASS="******",  # Use the 8b10b encoder
            p_CHX_DEC_BYPASS="******",  # Use the 8b10b decoder

            # CHX receive --------------------------------------------------------------------------
            # CHX RX ­— power management
            p_CHX_RPWDNB="0b1",
            i_CHX_FFC_RXPWDNB=1,

            # CHX RX ­— reset
            i_CHX_FFC_RRST=~self.rx_enable | reset.serdes_rx_reset,
            i_CHX_FFC_LANE_RX_RST=~self.rx_enable | reset.pcs_reset,

            # CHX RX ­— input
            i_CHX_HDINP=self._rx_pads.p,
            i_CHX_HDINN=self._rx_pads.n,
            p_CHX_REQ_EN="0b1",  # Enable equalizer
            p_CHX_REQ_LVL_SET="0b01",
            p_CHX_RX_RATE_SEL="0d09",  # Equalizer  pole position
            p_CHX_RTERM_RX={
                "5k-ohms": "0b00000",
                "80-ohms": "0b00001",
                "75-ohms": "0b00100",
                "70-ohms": "0b00110",
                "60-ohms": "0b01011",
                "50-ohms": "0b10011",
                "46-ohms": "0b11001",
                "wizard-50-ohms": "0d22"
            }["wizard-50-ohms"],
            p_CHX_RXIN_CM="0b11",  # CMFB (wizard value used)
            p_CHX_RXTERM_CM="0b10",  # RX Input (wizard value used)

            # CHX RX ­— clocking
            i_CHX_RX_REFCLK=self._pll.refclk,
            o_CHX_FF_RX_PCLK=rxoutclk,
            i_CHX_FF_RXI_CLK=ClockSignal("rx"),
            p_CHX_CDR_MAX_RATE="5.0",  # 5.0 Gbps
            p_CHX_RX_DCO_CK_DIV={
                32: "0b111",
                16: "0b110",
                8: "0b101",
                4: "0b100",
                2: "0b010",
                1: "0b000"
            }[1],  # DIV/1
            p_CHX_RX_GEAR_MODE="0b1",  # 1:2 gearbox
            p_CHX_FF_RX_H_CLK_EN="0b1",  # enable  DIV/2 output clock
            p_CHX_FF_RX_F_CLK_DIS="0b1",  # disable DIV/1 output clock
            p_CHX_SEL_SD_RX_CLK="0b1",  # FIFO driven by recovered clock
            p_CHX_AUTO_FACQ_EN="0b1",  # undocumented (wizard value used)
            p_CHX_AUTO_CALIB_EN="0b1",  # undocumented (wizard value used)
            p_CHX_PDEN_SEL="0b0",  # phase detector disabled on LOS
            p_CHX_DCOATDCFG="0b00",  # begin undocumented (sample code used)
            p_CHX_DCOATDDLY="0b00",
            p_CHX_DCOBYPSATD="0b1",
            p_CHX_DCOCALDIV="0b000",
            p_CHX_DCOCTLGI="0b011",
            p_CHX_DCODISBDAVOID="0b0",
            p_CHX_DCOFLTDAC="0b00",
            p_CHX_DCOFTNRG="0b001",
            p_CHX_DCOIOSTUNE="0b010",
            p_CHX_DCOITUNE="0b00",
            p_CHX_DCOITUNE4LSB="0b010",
            p_CHX_DCOIUPDNX2="0b1",
            p_CHX_DCONUOFLSB="0b100",
            p_CHX_DCOSCALEI="0b01",
            p_CHX_DCOSTARTVAL="0b010",
            p_CHX_DCOSTEP="0b11",  # end undocumented

            # CHX RX — loss of signal
            o_CHX_FFS_RLOS=rx_los,
            p_CHX_RLOS_SEL="0b1",
            p_CHX_RX_LOS_EN="0b0",
            p_CHX_RX_LOS_LVL="0b101",  # Lattice "TBD" (wizard value used)
            p_CHX_RX_LOS_CEQ="0b11",  # Lattice "TBD" (wizard value used)
            p_CHX_RX_LOS_HYST_EN="0b1",

            # CHX RX — loss of lock
            o_CHX_FFS_RLOL=rx_lol,

            # CHX RX — link state machine
            # Note that Lattice Diamond needs these in their provided bases (and string lengths!).
            # Changing their bases will work with the open toolchain, but will make Diamond mad.
            i_CHX_FFC_SIGNAL_DETECT=rx_align,
            o_CHX_FFS_LS_SYNC_STATUS=rx_lsm,
            p_CHX_ENABLE_CG_ALIGN="0b1",
            p_CHX_UDF_COMMA_MASK="0x0ff",  # compare the 8 lsbs
            p_CHX_UDF_COMMA_A=
            "0x003",  # "0b0000000011", # K28.1, K28.5 and K28.7
            p_CHX_UDF_COMMA_B=
            "0x07c",  # "0b0001111100", # K28.1, K28.5 and K28.7
            p_CHX_CTC_BYPASS="******",  # bypass CTC FIFO
            p_CHX_MIN_IPG_CNT="0b11",  # minimum interpacket gap of 4
            p_CHX_MATCH_2_ENABLE="0b0",  # 2 character skip matching
            p_CHX_MATCH_4_ENABLE="0b0",  # 4 character skip matching
            p_CHX_CC_MATCH_1="0x000",
            p_CHX_CC_MATCH_2="0x000",
            p_CHX_CC_MATCH_3="0x000",
            p_CHX_CC_MATCH_4="0x000",

            # CHX RX — data
            **{"o_CHX_FF_RX_D_%d" % n: rx_bus[n]
               for n in range(len(rx_bus))},

            # CHX transmit -------------------------------------------------------------------------
            # CHX TX — power management
            p_CHX_TPWDNB="0b1",
            i_CHX_FFC_TXPWDNB=1,

            # CHX TX ­— reset
            i_D_FFC_TRST=~self.tx_enable | reset.serdes_tx_reset,
            i_CHX_FFC_LANE_TX_RST=~self.tx_enable | reset.pcs_reset,

            # CHX TX ­— output
            o_CHX_HDOUTP=self._tx_pads.p,
            o_CHX_HDOUTN=self._tx_pads.n,
            p_CHX_TXAMPLITUDE="0d1000",  # 1000 mV
            p_CHX_RTERM_TX={
                "5k-ohms": "0b00000",
                "80-ohms": "0b00001",
                "75-ohms": "0b00100",
                "70-ohms": "0b00110",
                "60-ohms": "0b01011",
                "50-ohms": "0b10011",
                "46-ohms": "0b11001",
                "wizard-50-ohms": "0d19"
            }["50-ohms"],
            p_CHX_TDRV_SLICE0_CUR="0b011",  # 400 uA
            p_CHX_TDRV_SLICE0_SEL="0b01",  # main data
            p_CHX_TDRV_SLICE1_CUR="0b000",  # 100 uA
            p_CHX_TDRV_SLICE1_SEL="0b00",  # power down
            p_CHX_TDRV_SLICE2_CUR="0b11",  # 3200 uA
            p_CHX_TDRV_SLICE2_SEL="0b01",  # main data
            p_CHX_TDRV_SLICE3_CUR="0b10",  # 2400 uA
            p_CHX_TDRV_SLICE3_SEL="0b01",  # main data
            p_CHX_TDRV_SLICE4_CUR="0b00",  # 800 uA
            p_CHX_TDRV_SLICE4_SEL="0b00",  # power down
            p_CHX_TDRV_SLICE5_CUR="0b00",  # 800 uA
            p_CHX_TDRV_SLICE5_SEL="0b00",  # power down

            # CHX TX ­— clocking
            o_CHX_FF_TX_PCLK=txoutclk,
            i_CHX_FF_TXI_CLK=ClockSignal("tx"),
            p_CHX_TX_GEAR_MODE="0b1",  # 1:2 gearbox
            p_CHX_FF_TX_H_CLK_EN="0b1",  # enable  DIV/2 output clock
            p_CHX_FF_TX_F_CLK_DIS="0b1",  # disable DIV/1 output clock

            # CHX TX — data
            **{"i_CHX_FF_TX_D_%d" % n: tx_bus[n]
               for n in range(len(tx_bus))},

            # SCI interface.
            #**{"i_D_SCIWDATA%d" % n: sci.sci_wdata[n] for n in range(8)},
            #**{"i_D_SCIADDR%d"   % n: sci.sci_addr[n] for n in range(6)},
            #**{"o_D_SCIRDATA%d" % n: sci.sci_rdata[n] for n in range(8)},
            #i_D_SCIENAUX  = sci.dual_sel,
            #i_D_SCISELAUX = sci.dual_sel,
            #i_CHX_SCIEN   = sci.chan_sel,
            #i_CHX_SCISEL  = sci.chan_sel,
            #i_D_SCIRD     = sci.sci_rd,
            #i_D_SCIWSTN   = sci.sci_wrn,

            # Out-of-band signaling Rx support.
            p_CHX_LDR_RX2CORE_SEL="0b1",  # Enables low-speed out-of-band input.
            o_CHX_LDR_RX2CORE=self.rx_gpio,

            # Out-of-band signaling Tx support.
            p_CHX_LDR_CORE2TX_SEL=
            "0b0",  # Uses CORE2TX_EN to enable out-of-band output.
            i_CHX_LDR_CORE2TX=self.tx_gpio,
            i_CHX_FFC_LDR_CORE2TX_EN=self.tx_gpio_en)

        # Translate the 'CHX' string to the correct channel name in each of our SerDes parameters,
        # and create our SerDes instance.
        serdes_params = {
            k.replace("CHX", f"CH{self._channel}"): v
            for (k, v) in serdes_params.items()
        }
        m.submodules.serdes = serdes = Instance("DCUA", **serdes_params)

        # Bind our SerDes to the correct location inside the FPGA.
        serdes.attrs["LOC"] = "DCU{}".format(self._dual)
        serdes.attrs["CHAN"] = "CH{}".format(self._channel)
        serdes.attrs["BEL"] = "X42/Y71/DCU"

        #
        # TX and RX datapaths (SerDes <-> stream conversion)
        #
        sink = self.sink
        source = self.source

        m.d.comb += [
            # Grab our received data directly from our SerDes; modifying things to match the
            # SerDes Rx bus layout, which squishes status signals between our two geared words.
            source.data[0:8].eq(rx_bus[0:8]),
            source.data[8:16].eq(rx_bus[12:20]),
            source.ctrl[0].eq(rx_bus[8]),
            source.ctrl[1].eq(rx_bus[20]),
            source.valid.eq(1),

            # Stick the data we'd like to transmit into the SerDes; again modifying things to match
            # the transmit bus layout.
            tx_bus[0:8].eq(sink.data[0:8]),
            tx_bus[12:20].eq(sink.data[8:16]),
            tx_bus[8].eq(sink.ctrl[0]),
            tx_bus[20].eq(sink.ctrl[1]),
            sink.ready.eq(1)
        ]

        return m
Exemplo n.º 24
0
    def elaborate(self, platform):
        m = Module()
        # register submodules (and make a local reference without self)
        m.submodules.rdport = rdport = \
            self.line_buf.read_port(domain=self.led_domain, transparent=False)
        # we want each enable to cover one channel so we can write one channel
        # at a time
        m.submodules.wrport = wrport = \
            self.line_buf.write_port(granularity=self.pd.bpp)
        m.submodules.col_ctr = col_ctr = self.col_ctr

        # zeroth, we need to know which row the frame generator is currently
        # working on so we can work on the next one. because it's in the LED
        # domain and we're in sync, we have to synchronize it to us. there will
        # be a few cycles of delay, but it's okay unless the pixel reader is
        # like 99.99% busy.
        fg_row = Signal(self.pd.row_bits)
        fg_row_syncer = FFSynchronizer(i=self.i_row,
                                       o=fg_row,
                                       o_domain="sync",
                                       reset_less=False,
                                       stages=3)
        m.submodules += fg_row_syncer

        # first, give the frame generator the pixels it wants
        fg_data = Signal(6 * self.pd.bpp)
        m.d.comb += [
            # they get read directly from the buffer in the LED domain
            rdport.addr.eq(Cat(self.i_col, self.i_row[0])),
            fg_data.eq(rdport.data),
        ]
        # once the memory is read, we have to select the bits from the pixels
        # that the frame generator actually wants.
        with m.Switch(self.i_bit):
            for bn in range(self.pd.bpp):  # which bit from the pixel
                with m.Case(bn):
                    for cn in range(6):  # which pixel channel
                        m.d.comb += self.o_pixel[cn].eq(
                            fg_data[cn * self.pd.bpp + bn])

        # second, we need to read pixels to fill the buffer for the next line
        rd_row = Signal(self.pd.row_bits)  # which row we're reading
        rd_chan = Signal(3)  # which channel we're reading. goes 0-2, 4-6.
        # combine into output physical address.
        # channel bits, column, row, display half bit
        m.d.comb += self.o_raddr.eq(
            Cat(rd_chan[:2], col_ctr.value, rd_row, rd_chan[-1]))

        should_count = Signal()  # should we count to the next pixel?
        m.d.comb += self.col_ctr.enable.eq(should_count)
        should_read = Signal()  # are we intending to read a pixel?
        m.d.comb += self.o_re.eq(should_read)
        # this is probably not logically correct!! it needs to be fixed if we
        # ever plan to actually stall the pixel reader.
        done_reading = Signal()  # did the pixel read finish?
        m.d.comb += done_reading.eq(self.i_rack)

        # once the pixel read finishes, we want to write it to the buffer.
        # we replicate the read channel data across the 6 memory channels, then
        # only enable the write for the channel that the data is for.
        # of course, if gamma is enabled, we pass the data through the gamma
        # correction table before writing it.

        # if we are done reading this cycle, use the data channel and address
        # from last cycle because there is at least a 1 cycle latency through
        # the read port, and we want to change that stuff this cycle so we can
        # get another channel ASAP.
        rd_wchan = Signal(3)
        rd_waddr = Signal.like(wrport.addr)
        m.d.sync += [
            rd_wchan.eq(rd_chan),
            rd_waddr.eq(Cat(col_ctr.value, rd_row[0])),
        ]
        if self.gp.gamma is None:
            m.d.comb += wrport.data.eq(Repl(self.i_rdata, 6))
            # write data at the address of the current pixel, in the appropriate
            # buffer half
            m.d.comb += wrport.addr.eq(rd_waddr)
            # enable the appropriate channel for writing
            for ch in range(6):
                # we index channels from 0 to 5 here, but rd_wchan goes 0-2, 4-6
                # because we skip 3 to keep addressing nice.
                m.d.comb += wrport.en[ch].eq(
                    done_reading & (rd_wchan == (ch if ch < 3 else ch + 1)))
        else:
            # the above, but through the gamma table
            g_rdport = self.gamma_table.read_port()
            m.submodules += g_rdport
            # buffer everything else an extra cycle to account for the table
            g_waddr = Signal.like(rd_waddr)
            g_wchan = Signal.like(rd_wchan)
            g_done_reading = Signal.like(done_reading)
            m.d.comb += [
                g_rdport.addr.eq(self.i_rdata),
                wrport.data.eq(Repl(g_rdport.data, 6)),
                wrport.addr.eq(g_waddr),
            ]
            m.d.sync += [
                g_waddr.eq(rd_waddr),
                g_wchan.eq(rd_wchan),
                g_done_reading.eq(done_reading),
            ]
            for ch in range(6):
                m.d.comb += wrport.en[ch].eq(
                    g_done_reading & (g_wchan == (ch if ch < 3 else ch + 1)))

        # FSM generates the channel addresses and manages starting/ending reads
        with m.FSM("WAIT"):
            with m.State("WAIT"):  # wait for next line to begin
                # i.e. the frame generator is reading the line we just wrote to
                with m.If(fg_row[0] == rd_row[0]):
                    # so start working on the next line
                    m.d.sync += rd_row.eq(fg_row + 1)
                    # start at the first pixel
                    m.d.comb += col_ctr.reset.eq(1)
                    # and get back into it
                    m.next = "P0R"

            with m.State("P0R"):  # top physical pixel's red channel
                m.d.comb += [
                    rd_chan.eq(0),  # which is channel 0
                    should_read.eq(1),
                ]
                with m.If(done_reading):  # have we got the data?
                    # go to next pixel. writing is handled outside state machine
                    m.next = "P0G"

            with m.State("P0G"):  # top physical pixel's green channel
                m.d.comb += [
                    rd_chan.eq(1),
                    should_read.eq(1),
                ]
                with m.If(done_reading):
                    m.next = "P0B"

            with m.State("P0B"):  # top physical pixel's blue channel
                m.d.comb += [
                    rd_chan.eq(2),
                    should_read.eq(1),
                ]
                with m.If(done_reading):
                    m.next = "P1R"

            with m.State("P1R"):  # bottom physical pixel's red channel
                m.d.comb += [
                    rd_chan.eq(4),
                    should_read.eq(1),
                ]
                with m.If(done_reading):
                    m.next = "P1G"

            with m.State("P1G"):  # bottom physical pixel's green channel
                m.d.comb += [
                    rd_chan.eq(5),
                    should_read.eq(1),
                ]
                with m.If(done_reading):
                    m.next = "P1B"

            with m.State("P1B"):  # bottom physical pixel's blue channel
                m.d.comb += [
                    rd_chan.eq(6),
                    should_read.eq(1),
                ]
                with m.If(done_reading):
                    # address next pixel in the line
                    m.d.comb += should_count.eq(1)
                    with m.If(col_ctr.at_max):  # done with this line?
                        m.next = "WAIT"
                    with m.Else():
                        m.next = "P0R"

        return m
Exemplo n.º 25
0
        def rx():
            # count out the bits we're receiving (including start and stop)
            bit_ctr = Signal(range(self.char_bits + 2 - 1))
            # shift in the data bits, plus start and stop
            in_buf = Signal(self.char_bits + 2)
            # count cycles per baud
            baud_ctr = Signal(16)

            # since the rx pin is attached to arbitrary external logic, we
            # should sync it with our domain first.
            i_rx = Signal(reset=1)
            rx_sync = FFSynchronizer(self.i_rx, i_rx, reset=1)
            m.submodules.rx_sync = rx_sync

            with m.FSM("IDLE"):
                with m.State("IDLE"):
                    # has the receive line been asserted? (todo, maybe debounce
                    # this a couple cycles? is that even a problem?)
                    with m.If(~i_rx):
                        m.d.sync += [
                            # start counting down the bits
                            bit_ctr.eq(self.char_bits + 2 - 1),
                            # and tell the user that we're actively receiving
                            r0_rx_active.eq(1),
                        ]
                        # start the baud counter at half the baud time. this way
                        # we end up halfway through the start bit when we next
                        # sample and can make sure that rx is still asserted.
                        # we're also then lined up to sample the rest of the
                        # bits in the middle.
                        m.d.sync += baud_ctr.eq(r0_baud_divisor >> 1)
                        # then just receive the start bit like any other
                        m.next = "RECV"

                with m.State("RECV"):
                    m.d.sync += baud_ctr.eq(baud_ctr - 1)
                    with m.If(baud_ctr == 0):
                        # sample the bit once it's time. we shift bits into the
                        # MSB so the first bit ends up at the LSB once we are
                        # done.
                        m.d.sync += in_buf.eq(Cat(in_buf[1:], i_rx))
                        with m.If(bit_ctr == 0):  # this is the stop bit?
                            # yes, sample it (this cycle) and finish up next
                            m.next = "FINISH"
                        with m.Else():
                            # no, wait to receive another bit
                            m.d.sync += [
                                baud_ctr.eq(r0_baud_divisor),
                                bit_ctr.eq(bit_ctr - 1),
                            ]

                with m.State("FINISH"):
                    # make sure that the start bit is 0 and the stop bit is 1,
                    # like the standard prescribes.
                    with m.If((in_buf[0] == 0) & (in_buf[-1] == 1)):
                        # store the data to the "FIFO" if we have space
                        with m.If(r3_rx_empty.value):
                            # minus start and stop bits
                            m.d.sync += r3_rx_data.eq(in_buf[1:-1])
                            m.d.comb += r3_rx_empty.reset.eq(1)
                        with m.Else():
                            m.d.comb += r1_rx_overflow.set.eq(1)
                    with m.Else():
                        # we didn't actually receive a character. let
                        # the user know that something bad happened.
                        m.d.comb += r1_rx_error.set.eq(1)
                    # but we did finish receiving no matter what happened
                    m.d.sync += r0_rx_active.eq(0)
                    m.next = "IDLE"
Exemplo n.º 26
0
    def elaborate(self, platform):
        m = Module()

        # count out the bits we're receiving (including start and stop)
        bit_ctr = Signal(range(8 + 2 - 1))
        # shift in the data bits, plus start and stop
        in_buf = Signal(8 + 2)
        # count cycles per baud
        baud_ctr = Signal(range(self.divisor))

        # the data buf is connected directly. it's only valid for the cycle we
        # say it is though.
        m.d.comb += self.o_data.eq(in_buf[1:-1])

        # since the rx pin is attached to arbitrary external logic, we must sync
        # it with our domain first.
        i_rx = Signal(reset=1)
        rx_sync = FFSynchronizer(self.i_rx, i_rx, reset=1)
        m.submodules.rx_sync = rx_sync

        with m.FSM("IDLE"):
            with m.State("IDLE"):
                # has the receive line been asserted?
                with m.If(~i_rx):
                    m.d.sync += [
                        # start counting down the bits
                        bit_ctr.eq(8 + 2 - 1),
                        # we are now active!
                        self.o_active.eq(1),
                    ]
                    # start the baud counter at half the baud time. this way we
                    # end up halfway through the start bit when we next sample
                    # so we can make sure the start bit is still there. we're
                    # also then lined up to sample the rest of the bits in the
                    # middle.
                    m.d.sync += baud_ctr.eq(self.divisor >> 1)
                    # now we just receive the start bit like any other
                    m.next = "RECV"

            with m.State("RECV"):
                m.d.sync += baud_ctr.eq(baud_ctr - 1)
                with m.If(baud_ctr == 0):
                    # sample the bit once it's time. we shift bits into the MSB
                    # so the first bit ends up at the LSB once we are done.
                    m.d.sync += in_buf.eq(Cat(in_buf[1:], i_rx))
                    with m.If(bit_ctr == 0):  # this is the stop bit?
                        # yes, sample it (this cycle) and finish up next
                        m.next = "FINISH"
                    with m.Else():
                        # no, wait to receive another bit
                        m.d.sync += [
                            baud_ctr.eq(self.divisor),
                            bit_ctr.eq(bit_ctr - 1),
                        ]

            with m.State("FINISH"):
                # make sure that the start bit is 0 and the stop bit is 1, like
                # the standard prescribes.
                with m.If((in_buf[0] == 0) & (in_buf[-1] == 1)):
                    # tell the user we've got something
                    m.d.comb += self.o_we.eq(1)
                with m.Else():
                    # we didn't correctly receive the character. let the user
                    # know that something bad happened.
                    m.d.comb += self.o_error.eq(1)
                # but we did finish receiving, no matter the outcome
                m.d.sync += self.o_active.eq(0)
                m.next = "IDLE"
                # technically, there's still half a bit time until the stop bit
                # is over, but that's ok. the rx line is deasserted during that
                # time so we won't accidentally start receiving another bit.

        return m
Exemplo n.º 27
0
    def elaborate(self, platform):
        m = Module()

        pads = self._pads

        if hasattr(pads, "ce_t"):
            m.d.comb += [
                pads.ce_t.oe.eq(1),
                pads.ce_t.o.eq(~self.ce),
            ]
        if hasattr(pads, "oe_t"):
            m.d.comb += [
                pads.oe_t.oe.eq(1),
                pads.oe_t.o.eq(~self.oe),
            ]
        if hasattr(pads, "we_t"):
            m.d.comb += [
                pads.we_t.oe.eq(1),
                pads.we_t.o.eq(~self.we),
            ]

        m.d.comb += [
            pads.dq_t.oe.eq(~self.oe),
            pads.dq_t.o.eq(self.d),
        ]
        m.submodules += FFSynchronizer(pads.dq_t.i, self.q)

        m.d.comb += [
            pads.a_t.oe.eq(1),
            pads.a_t.o.eq(self.a),  # directly drive low bits
        ]

        if hasattr(pads, "a_clk_t") and hasattr(pads, "a_si_t"):
            a_clk = Signal(reset=1)
            a_si = Signal()
            a_lat = Signal(reset=0) if hasattr(pads, "a_lat_t") else None
            m.d.comb += [
                pads.a_clk_t.oe.eq(1),
                pads.a_clk_t.o.eq(a_clk),
                pads.a_si_t.oe.eq(1),
                pads.a_si_t.o.eq(a_si),
            ]
            if a_lat is not None:
                m.d.comb += [pads.a_lat_t.oe.eq(1), pads.a_lat_t.o.eq(a_lat)]

            # "sa" is the sliced|shifted address, refering to the top-most bits
            sa_input = self.a[len(pads.a_t.o):]
            # This represents a buffer of those high address bits,
            # not to be confused with the latch pin.
            sa_latch = Signal(self.a_bits - len(pads.a_t.o))

            sh_cyc = math.ceil(platform.default_clk_frequency / self._sh_freq)
            timer = Signal(range(sh_cyc), reset=sh_cyc - 1)
            count = Signal(range(len(sa_latch) + 1))
            first = Signal(reset=1)

            with m.FSM():
                with m.State("READY"):
                    m.d.sync += first.eq(0)
                    with m.If((sa_latch == sa_input) & ~first):
                        m.d.comb += self.rdy.eq(1)
                    with m.Else():
                        m.d.sync += count.eq(len(sa_latch))
                        m.d.sync += sa_latch.eq(sa_input)
                        m.next = "SHIFT"

                with m.State("SHIFT"):
                    with m.If(timer == 0):
                        m.d.sync += timer.eq(timer.reset)
                        m.d.sync += a_clk.eq(~a_clk)
                        with m.If(a_clk):
                            m.d.sync += a_si.eq(sa_latch[-1])
                            m.d.sync += count.eq(count - 1)
                        with m.Else():
                            m.d.sync += sa_latch.eq(sa_latch.rotate_left(1))
                            with m.If(count == 0):
                                if a_lat is None:
                                    m.next = "READY"
                                else:
                                    m.next = "LATCH-1"
                    with m.Else():
                        m.d.sync += timer.eq(timer - 1)

                if a_lat is not None:
                    with m.State("LATCH-1"):
                        m.d.sync += a_lat.eq(1)
                        with m.If(timer == 0):
                            m.d.sync += timer.eq(timer.reset)
                            m.next = "LATCH-2"
                        with m.Else():
                            m.d.sync += timer.eq(timer - 1)

                    with m.State("LATCH-2"):
                        with m.If(timer == 0):
                            m.d.sync += timer.eq(timer.reset)
                            m.d.sync += a_lat.eq(0)
                            m.next = "READY"
                        with m.Else():
                            m.d.sync += timer.eq(timer - 1)

        else:
            m.d.comb += self.rdy.eq(1)

        return m
Exemplo n.º 28
0
    def elaborate(self, platform):
        m = Module()

        hispi_6_in = "{}_x6_in".format(self.hispi_domain)
        m.domains += ClockDomain(hispi_6_in)
        m.d.comb += ClockSignal(hispi_6_in).eq(self.hispi_clk)
        m.d[hispi_6_in] += self.hispi_x6_in_domain_counter.eq(
            self.hispi_x6_in_domain_counter + 1)

        mul = 3
        pll = m.submodules.pll = Mmcm(300e6,
                                      mul,
                                      1,
                                      input_domain=hispi_6_in.format(
                                          self.hispi_domain))
        pll.output_domain("{}_x6".format(self.hispi_domain), mul * 1)
        pll.output_domain("{}_x3".format(self.hispi_domain), mul * 2)
        pll.output_domain("{}_x2".format(self.hispi_domain), mul * 3)
        pll.output_domain("{}".format(self.hispi_domain), mul * 6)

        for lane in range(0, len(self.hispi_lanes)):
            iserdes = m.submodules["hispi_iserdes_" + str(lane)] = _ISerdes(
                data_width=6,
                data_rate="DDR",
                serdes_mode="master",
                interface_type="networking",
                num_ce=1,
                iobDelay="none",
            )

            m.d.comb += iserdes.d.eq(self.hispi_lanes[lane])
            m.d.comb += iserdes.ce[1].eq(1)
            m.d.comb += iserdes.clk.eq(
                ClockSignal("{}_x6".format(self.hispi_domain)))
            m.d.comb += iserdes.clkb.eq(
                ~ClockSignal("{}_x6".format(self.hispi_domain)))
            m.d.comb += iserdes.rst.eq(
                ResetSignal("{}_x6".format(self.hispi_domain)))
            m.d.comb += iserdes.clkdiv.eq(
                ClockSignal("{}_x2".format(self.hispi_domain)))

            data = Signal(12)
            iserdes_output = Signal(6)
            m.d.comb += iserdes_output.eq(
                Cat(iserdes.q[j] for j in range(1, 7)))

            hispi_x2 = "{}_x2".format(self.hispi_domain)
            lower_upper_half = Signal()
            m.d[hispi_x2] += lower_upper_half.eq(~lower_upper_half)
            with m.If(lower_upper_half):
                m.d[hispi_x2] += data[6:12].eq(iserdes_output)
            with m.Else():
                m.d[hispi_x2] += data[0:6].eq(iserdes_output)

            data_in_hispi_domain = Signal(12)
            m.submodules["data_cdc_{}".format(lane)] = FFSynchronizer(
                data, data_in_hispi_domain, o_domain=self.hispi_domain)

            hispi_domain = m.d[self.hispi_domain]
            bitslip = Signal()
            was_bitslip = Signal()
            hispi_domain += was_bitslip.eq(bitslip)
            with m.If(self.bitslip[lane] & ~was_bitslip & self.enable_bitslip):
                hispi_domain += bitslip.eq(1)
            with m.Else():
                hispi_domain += bitslip.eq(0)

            serdes_or_emulated_bitslip = Signal()
            with m.If(bitslip):
                hispi_domain += serdes_or_emulated_bitslip.eq(
                    ~serdes_or_emulated_bitslip)

            m.d.comb += iserdes.bitslip.eq(bitslip
                                           & serdes_or_emulated_bitslip)

            data_order_index = Signal(range(4))
            with m.If(bitslip & ~serdes_or_emulated_bitslip):
                hispi_domain += data_order_index.eq(data_order_index + 1)

            data_order = StatusSignal(range(16))
            setattr(self, "data_order_{}".format(lane), data_order)
            m.d.comb += data_order.eq(Array((1, 4, 9, 12))[data_order_index])

            current = Signal(12)
            last = Signal(12)
            m.d.comb += current.eq(data_in_hispi_domain)
            hispi_domain += last.eq(data_in_hispi_domain)
            reordered = Signal(12)
            parts = [current[0:6], current[6:12], last[0:6], last[6:12]]
            for cond, i in iterator_with_if_elif(range(16), m):
                with cond(data_order == i):
                    first = parts[i % 4]
                    second = parts[i // 4]
                    m.d.comb += reordered.eq(Cat(first, second))

            with m.If(self.word_reverse):
                m.d.comb += self.out[lane].eq(
                    Cat(reordered[i] for i in range(12)))
            with m.Else():
                m.d.comb += self.out[lane].eq(
                    Cat(reordered[i] for i in reversed(range(12))))

            out_status_signal = StatusSignal(12, name="out_{}".format(lane))
            setattr(self, "out_{}".format(lane), out_status_signal)
            m.d.comb += out_status_signal.eq(data_in_hispi_domain)

        return m
Exemplo n.º 29
0
Arquivo: cdc.py Projeto: Fatsie/nmigen
from nmigen import *
from nmigen.lib.cdc import FFSynchronizer
from nmigen.cli import main

i, o = Signal(name="i"), Signal(name="o")
m = Module()
m.submodules += FFSynchronizer(i, o)

if __name__ == "__main__":
    main(m, ports=[i, o])
Exemplo n.º 30
0
    def elaborate(self, platform):
        m = Module()

        # temp clock setup
        platform.ps7.fck_domain(200e6, self.domain + "_delay_ref")
        m.domains += ClockDomain(self.domain + "_ctrl")
        m.d.comb += ClockSignal(self.domain + "_ctrl").eq(
            ClockSignal(platform.csr_domain))

        phy = m.submodules.phy = self.phy
        trainer = m.submodules.trainer = self.trainer
        m.submodules += FFSynchronizer(trainer.lane_pattern,
                                       phy.lane_pattern,
                                       o_domain=self.domain + "_hword")

        m.d.comb += [
            phy.outclk.eq(self.lvds_outclk),
            phy.lanes.eq(Cat(self.lanes, self.lane_ctrl)),
            phy.lane_delay_reset.eq(trainer.lane_delay_reset),
            phy.lane_delay_inc.eq(trainer.lane_delay_inc),
            phy.lane_bitslip.eq(trainer.lane_bitslip),
            phy.outclk_delay_reset.eq(trainer.outclk_delay_reset),
            phy.outclk_delay_inc.eq(trainer.outclk_delay_inc),
            phy.halfslip.eq(trainer.halfslip),
            trainer.lane_match.eq(phy.lane_match),
            trainer.lane_mismatch.eq(phy.lane_mismatch),
        ]

        # synchronize valid signal to output domain
        lanes_valid = Signal()
        valid_sync = m.submodules.valid_sync = PulseSynchronizer(
            self.domain + "_hword", self.output_domain)
        trained = Signal()
        m.submodules += FFSynchronizer(trainer.trained,
                                       trained,
                                       o_domain=self.domain + "_hword")
        m.d.comb += [
            valid_sync.i.eq(phy.output_valid & trained),
            lanes_valid.eq(valid_sync.o)
        ]

        # information about the "current" pixel
        data_lanes = Signal(len(self.lanes) * self.bits)
        pixel_valid = Signal()
        line_valid = Signal()
        frame_valid = Signal()

        # information about the next pixel, i.e. what we grab from the PHY
        next_data_lanes = Signal(len(self.lanes) * self.bits)
        next_pixel_valid = Signal()
        next_line_valid = Signal()
        next_frame_valid = Signal()

        # on the rising edge of the valid signal, grab the next pixel from the
        # PHY. this is a clock domain crossing, but since the source clock is in
        # phase with the output clock, we will be okay without synchronization.
        # note that the valid signal rises only once every `self.bits/2` clocks.
        with m.If(lanes_valid):
            m.d[self.output_domain] += [
                next_data_lanes.eq(Cat(*phy.output[:-1])),
                Cat(next_pixel_valid, next_line_valid,
                    next_frame_valid).eq(phy.output[-1]),
                # move the next pixel to the current pixel
                data_lanes.eq(next_data_lanes),
                Cat(pixel_valid, line_valid, frame_valid).eq(
                    Cat(next_pixel_valid, next_line_valid, next_frame_valid))
            ]

        # once we've got that, generate the output stream information
        output = ImageStream(len(self.lanes) * self.bits)
        m.d.comb += output.payload.eq(data_lanes)
        with m.If(Fell(m, lanes_valid, self.output_domain)):
            m.d.comb += [
                output.valid.eq(pixel_valid),
                # if the next pixel isn't a valid line or frame, this pixel is
                # the last one of the current line/frame
                output.line_last.eq(line_valid & ~next_line_valid),
                output.frame_last.eq(frame_valid & ~next_frame_valid),
            ]

        # we are naughty and do not respect the ready signal, so add a buffer
        # to handle that part of the contract for us
        buffer = m.submodules.buffer = DomainRenamer(self.output_domain)(
            StreamBuffer(output))
        m.d.comb += self.output.connect_upstream(buffer.output)

        return m