Exemple #1
0
    def elaborate(self, platform):
        m = Module()

        wavelet = m.submodules.wavelet = MultiStageWavelet2D(self.input,
                                                             self.width,
                                                             self.height,
                                                             stages=3)
        packetizer = m.submodules.packetizer = ImageStream2PacketizedStream(
            wavelet.output)

        bit_stuffing_input = VariableWidthStream(self.input.payload.shape(),
                                                 reset_width=len(
                                                     self.input.payload))
        with m.If(packetizer.output.is_hf):
            rle_input = PacketizedStream()
            m.d.comb += rle_input.connect_upstream(packetizer.output)
            rle = m.submodules.rle = ZeroRleEncoder(rle_input,
                                                    self.possible_run_lengths)
            huffman = m.submodules.huffman = HuffmanEncoder(rle.output)
            m.d.comb += bit_stuffing_input.connect_upstream(huffman.output)
        with m.Else():
            m.d.comb += bit_stuffing_input.connect_upstream(packetizer.output)

        bit_stuffing = m.submodules.bit_stuffing = BitStuffer(
            bit_stuffing_input, len(self.output.payload))
        m.d.comb += self.output.connect_upstream(bit_stuffing.output)

        return m
Exemple #2
0
class ConsolePacketSource(Elaboratable):
    def __init__(self, data_width=8, max_packet_size=1024):
        self.max_packet_size = max_packet_size

        self.reset = ControlSignal()
        self.packet_length = ControlSignal(range(max_packet_size))
        self.read_ptr = StatusSignal(range(max_packet_size))
        self.done = StatusSignal(reset=1)
        self.memory = SocMemory(
            width=data_width, depth=self.max_packet_size,
            soc_read=False,  attrs=dict(syn_ramstyle="block_ram")
        )

        self.output = PacketizedStream(data_width)

    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

    @driver_method
    def write_packet(self, packet, timeout=0):
        from time import sleep
        for i in range(int(timeout * 10)):
            if self.done:
                break
            sleep(0.1)
        assert self.done
        for i, word in enumerate(packet):
            self.memory[i] = word
        self.packet_length = len(packet) - 1
        self.reset = not self.reset
Exemple #3
0
class ImageStream2PacketizedStream(Elaboratable):
    """Convert an ImageStream to a packetized Stream by producing one packet per frame"""
    def __init__(self, input: ImageStream):
        self.input = input
        self.output = PacketizedStream(self.input.payload.shape(),
                                       name="packetized_image_stream")

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

        m.d.comb += self.output.connect_upstream(
            self.input, exclude=["last", "frame_last", "line_last"])
        m.d.comb += self.output.last.eq(self.input.frame_last)

        return m
Exemple #4
0
class WaveletCompressor(Elaboratable):
    """compresses an image stream using a wavelet compression algorithm"""
    def __init__(self, input: ImageStream, width, height):
        self.input = input
        self.width = width
        self.height = height

        max_input_word = 2**len(self.input.payload)
        self.possible_run_lengths = [2, 4, 8, 16, 32, 64, 128, 256]
        self.huffmann_frequencies = {
            **{k: 1
               for k in range(max_input_word)},
            **{
                k: 10
                for k in range(max_input_word, max_input_word + len(self.possible_run_lengths))
            }
        }

        self.output = PacketizedStream(self.input.payload.shape())

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

        wavelet = m.submodules.wavelet = MultiStageWavelet2D(self.input,
                                                             self.width,
                                                             self.height,
                                                             stages=3)
        packetizer = m.submodules.packetizer = ImageStream2PacketizedStream(
            wavelet.output)

        bit_stuffing_input = VariableWidthStream(self.input.payload.shape(),
                                                 reset_width=len(
                                                     self.input.payload))
        with m.If(packetizer.output.is_hf):
            rle_input = PacketizedStream()
            m.d.comb += rle_input.connect_upstream(packetizer.output)
            rle = m.submodules.rle = ZeroRleEncoder(rle_input,
                                                    self.possible_run_lengths)
            huffman = m.submodules.huffman = HuffmanEncoder(rle.output)
            m.d.comb += bit_stuffing_input.connect_upstream(huffman.output)
        with m.Else():
            m.d.comb += bit_stuffing_input.connect_upstream(packetizer.output)

        bit_stuffing = m.submodules.bit_stuffing = BitStuffer(
            bit_stuffing_input, len(self.output.payload))
        m.d.comb += self.output.connect_upstream(bit_stuffing.output)

        return m
Exemple #5
0
class DsiPhy(Elaboratable):
    def __init__(self, resource, num_lanes, ddr_domain, ck_domain):
        self.resource = resource
        self.num_lanes = num_lanes
        self.ddr_domain = ddr_domain
        self.ck_domain = ck_domain

        self.control_input = PacketizedStream(8)
        self.control_output = PacketizedStream(8)
        self.hs_input = PacketizedStream(8 * num_lanes)
        self.request_hs = ControlSignal()


    def elaborate(self, platform: Platform):
        resource = platform.request(*self.resource, xdr={"hs_ck": 2, **{f"hs_d{i}": 2 for i in range(self.num_lanes)}})

        m = Module()

        lanes = []
        for i in range(2):
            lane = DPhyDataLane(
                lp_pins=getattr(resource, f"lp_d{i}"),
                hs_pins=getattr(resource, f"hs_d{i}"),
                can_lp=(i == 0),
                ddr_domain=self.ddr_domain
            )
            m.submodules[f"lane_d{i}"] = lane
            lanes.append(lane)

        lane0 = lanes[0]
        m.d.comb += lane0.control_input.connect_upstream(self.control_input)
        m.d.comb += self.control_output.connect_upstream(lane0.control_output)

        m.d.comb += self.hs_input.ready.eq(lane0.hs_input.ready)
        for i, lane in enumerate(lanes):
            m.d.comb += lane.hs_input.payload.eq(self.hs_input.payload[i * 8: (i+1) * 8])
            m.d.comb += lane.hs_input.valid.eq(self.hs_input.valid)
            m.d.comb += lane.hs_input.last.eq(self.hs_input.last)

        lane_ck = m.submodules.lane_ck = DPhyClockLane(resource.lp_ck, resource.hs_ck, ck_domain=self.ck_domain)
        m.d.comb += lane_ck.request_hs.eq(self.request_hs)

        return m
Exemple #6
0
class DPhyDataLane(Elaboratable):
    """ A mipi D-Phy Data lane that can handle bidirectional lp data transfer and unidirectional hs transfer.

    The `sync` domain of this module should run at 2x the LP Hold period. Eg if the hold period is 66ns, the sync domain should run at 30 Mhz
    (these are reasonable values btw). This is needed to be able to sample the incoming data during bus turnaround since there is no fixed phase relation (nyquist).
    """
    def __init__(self,
                 lp_pins: TristateIo,
                 hs_pins: TristateDdrIo,
                 initial_driving=True,
                 can_lp=False,
                 ddr_domain="sync"):
        self.lp_pins = lp_pins
        self.hs_pins = hs_pins
        self.can_lp = can_lp
        self.ddr_domain = ddr_domain

        if self.can_lp:
            # The control rx and control tx signals carry the raw escape mode packets.
            # A packet with length 1 and payload 0x0 indicates that we request a bus turnaround.
            # This in Not a valid MIPI Escape Entry Code so we simply repurpose that here.
            self.control_input = PacketizedStream(8)
            self.control_output = PacketizedStream(8)

        # the hs_input stream carries is polled to
        self.hs_input = PacketizedStream(8)

        self.is_hs = StatusSignal()
        self.is_driving = StatusSignal(
            reset=initial_driving) if can_lp else True
        self.bta_timeout = 1023
        self.bta_timeouts = StatusSignal(16)

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

        m.d.comb += self.lp_pins.oe.eq(self.is_driving)

        def delay_lp(cycles):
            return process_delay(m, cycles * 4)

        serializer_reset = Signal()
        m.d.comb += serializer_reset.eq(~(self.is_hs & self.is_driving))
        serializer = m.submodules.serializer = Serializer(
            self.hs_pins,
            width=8,
            ddr_domain=self.ddr_domain,
            reset=serializer_reset)

        @process_block
        def send_hs(data):
            return process_write_to_stream(m, serializer.input, payload=data)

        bta_timeout_counter = Signal(range(self.bta_timeout))
        bta_timeout_possible = Signal()

        with m.If(self.is_driving):
            lp = self.lp_pins.o[::-1]
            with m.FSM(name="tx_fsm") as fsm:
                fsm_status_reg(platform, m, fsm)
                with m.State("IDLE"):
                    m.d.comb += lp.eq(STOP)
                    if self.can_lp:
                        with m.If(self.control_input.valid
                                  & (self.control_input.payload == 0x00)
                                  & self.control_input.last):
                            m.d.comb += self.control_input.ready.eq(1)
                            m.next = "TURNAROUND_LP_REQUEST"
                        with m.Elif(self.control_input.valid):
                            m.next = "LP_REQUEST"
                        with m.Elif(self.hs_input.valid):
                            m.next = "HS_REQUEST"
                    else:
                        with m.If(self.hs_input.valid):
                            m.next = "HS_REQUEST"

                with Process(m, name="HS_REQUEST", to="HS_SEND") as p:
                    m.d.comb += lp.eq(HS_REQUEST)
                    p += delay_lp(1)
                    m.d.comb += lp.eq(BRIDGE)
                    p += delay_lp(3)
                    m.d.sync += self.is_hs.eq(1)
                    p += delay_lp(
                        4
                    )  # we are in HS-ZERO now and wait the constant part (150ns)
                    p += send_hs(Repl(0, 8))
                    p += send_hs(Repl(0, 8))
                    p += send_hs(Const(0b10111000, 8))

                with m.State("HS_SEND"):
                    with send_hs(self.hs_input.payload):
                        with m.If(self.hs_input.last):
                            m.next = "HS_END"
                        with m.Else():
                            m.next = "HS_SEND"
                            m.d.comb += self.hs_input.ready.eq(1)

                with Process(m, name="HS_END", to="IDLE") as p:
                    p += send_hs(Repl(~self.hs_input.payload[7], 8))
                    p += send_hs(Repl(~self.hs_input.payload[7], 8))
                    with m.If(NewHere(m)):
                        m.d.comb += self.hs_input.ready.eq(1)
                    p += m.If(serializer.is_idle)
                    m.d.sync += self.is_hs.eq(0)
                    p += process_delay(
                        m, 1
                    )  # TODO: this is currently tied to the way we do ddr (beaks when we change clock frequencies)
                    m.d.comb += lp.eq(STOP)
                    p += delay_lp(2)

                if self.can_lp:
                    with Process(m, name="LP_REQUEST", to="ESCAPE_0") as p:
                        m.d.comb += lp.eq(LP_REQUEST)
                        p += delay_lp(1)
                        m.d.comb += lp.eq(BRIDGE)
                        p += delay_lp(1)
                        m.d.comb += lp.eq(ESCAPE_REQUEST)
                        p += delay_lp(1)
                        m.d.comb += lp.eq(BRIDGE)
                        p += delay_lp(1)

                    for bit in range(8):
                        with m.State(f"ESCAPE_{bit}"):
                            with m.If(
                                    self.control_input.valid
                            ):  # after transmitting the first byte, this can be false. the mipi spec allows us to wait here (in space state)
                                with m.If(self.control_input.payload[bit]):
                                    m.d.comb += lp.eq(MARK_1)
                                with m.Else():
                                    m.d.comb += lp.eq(MARK_0)
                                with delay_lp(1):
                                    m.next = f"ESCAPE_{bit}_SPACE"
                        with m.State(f"ESCAPE_{bit}_SPACE"):
                            m.d.comb += lp.eq(SPACE)
                            if bit < 7:
                                with delay_lp(1):
                                    m.next = f"ESCAPE_{bit + 1}"
                            else:
                                with m.If(
                                        self.control_input.last
                                ):  # according to the stream contract, this may not change, until we assert ready :)
                                    with delay_lp(1):
                                        m.next = "ESCAPE_FINISH"
                                        m.d.comb += self.control_input.ready.eq(
                                            1)
                                with m.Else():
                                    with delay_lp(1):
                                        m.next = "ESCAPE_0"
                                        m.d.comb += self.control_input.ready.eq(
                                            1)

                    with Process(m, "ESCAPE_FINISH", to="IDLE") as p:
                        m.d.comb += lp.eq(MARK_1)
                        p += delay_lp(1)
                        m.d.comb += lp.eq(STOP)
                        p += delay_lp(
                            10
                        )  # TODO: reduce the delay; it is here to ease debugging :)

                    with Process(m,
                                 name="TURNAROUND_LP_REQUEST",
                                 to="TURNAROUND_RETURN") as p:
                        m.d.comb += lp.eq(LP_REQUEST)
                        p += delay_lp(1)
                        m.d.comb += lp.eq(BRIDGE)
                        p += delay_lp(1)
                        m.d.comb += lp.eq(TURNAROUND_REQUEST)
                        p += delay_lp(1)
                        m.d.comb += lp.eq(BRIDGE)
                        p += delay_lp(4)
                        m.d.sync += self.is_driving.eq(
                            0
                        )  # this makes us leave this FSM and enter the one below
                        m.d.sync += bta_timeout_counter.eq(0)
                        m.d.sync += bta_timeout_possible.eq(1)
                        p += process_delay(m, 1)

                    with Process(m, name="TURNAROUND_RETURN", to="IDLE") as p:
                        m.d.comb += lp.eq(STOP)
                        p += delay_lp(10)

        if self.can_lp:

            # we buffer the control output to be able to meet the stream contract
            control_output_unbuffered = PacketizedStream(8)
            control_output_buffer = m.submodules.control_output_buffer = StreamBuffer(
                control_output_unbuffered)
            m.d.comb += self.control_output.connect_upstream(
                control_output_buffer.output)

            with m.If(~self.is_driving):
                lp = Signal(2)
                m.submodules += FFSynchronizer(self.lp_pins.i[::-1], lp)

                with m.FSM(name="rx_fsm") as fsm:

                    def maybe_next(condition, next_state):
                        with m.If(condition):
                            m.next = next_state

                    def maybe_stop():
                        maybe_next(lp == STOP, "STOP")

                    with m.State("STOP"):
                        with m.If(bta_timeout_possible):
                            with m.If(bta_timeout_counter < self.bta_timeout):
                                m.d.sync += bta_timeout_counter.eq(
                                    bta_timeout_counter + 1)
                            with m.Else():
                                m.d.sync += self.bta_timeouts.eq(
                                    self.bta_timeouts + 1)
                                m.d.sync += self.is_driving.eq(1)
                                m.d.sync += bta_timeout_counter.eq(0)
                                m.d.sync += bta_timeout_possible.eq(0)
                        maybe_next(lp == LP_REQUEST, "AFTER-LP-REQUEST")
                        maybe_stop()

                    with m.State("AFTER-LP-REQUEST"):
                        m.d.sync += bta_timeout_possible.eq(0)
                        with m.If(lp == BRIDGE):
                            m.next = "AFTER-LP-REQUEST-BRIDGE"
                        maybe_stop()
                    with m.State("AFTER-LP-REQUEST-BRIDGE"):
                        with m.If(lp == ESCAPE_REQUEST):
                            m.next = "AFTER-ESCAPE-REQUEST"
                        with m.Elif(lp == TURNAROUND_REQUEST):
                            m.next = "AFTER-TURNAROUND-REQUEST"
                        maybe_stop()

                    with m.State("AFTER-TURNAROUND-REQUEST"):
                        with m.If(lp == BRIDGE):
                            with delay_lp(4):
                                m.next = "STOP"
                                m.d.sync += self.is_driving.eq(1)
                        maybe_stop()

                    with m.State("AFTER-ESCAPE-REQUEST"):
                        with m.If(lp == BRIDGE):
                            m.next = "ESCAPE_0"

                    # we keep track if we have already sent the currently or last received bit over our output stream.
                    # we send it either on the first bit of the next word or during the stop condition
                    outboxed = Signal(reset=1)

                    def maybe_finish_escape():
                        with m.If(lp == STOP):
                            m.next = "STOP"
                            m.d.sync += outboxed.eq(1)
                            with m.If(~outboxed):
                                m.d.comb += control_output_unbuffered.last.eq(
                                    1)
                                m.d.comb += control_output_unbuffered.valid.eq(
                                    1)

                    bit_value = Signal()
                    for bit in range(8):
                        with m.State(f"ESCAPE_{bit}"):
                            with m.If(lp == MARK_0):
                                m.d.sync += bit_value.eq(0)
                                m.next = f"ESCAPE_{bit}_SPACE"
                            with m.If(lp == MARK_1):
                                m.d.sync += bit_value.eq(1)
                                m.next = f"ESCAPE_{bit}_SPACE"
                            maybe_finish_escape()
                        with m.State(f"ESCAPE_{bit}_SPACE"):
                            with m.If(lp == SPACE):
                                if bit == 0:
                                    with m.If(~outboxed):
                                        m.d.comb += control_output_unbuffered.valid.eq(
                                            1)
                                        m.d.sync += outboxed.eq(1)

                                m.d.sync += control_output_unbuffered.payload[
                                    bit].eq(bit_value)
                                m.d.sync += outboxed.eq(0)
                                m.next = f"ESCAPE_{(bit + 1) % 8}"
                            maybe_finish_escape()

        return m
Exemple #7
0
class ImageStream2Dsi(Elaboratable):
    """
    Converts an ImageStream to a Packetized stream that can be fed into a DSI phy.
    Uses Non Burst Mode with Sync Events.
    """
    def __init__(self,
                 input: ImageStream,
                 num_lanes: int,
                 image_width=480,
                 debug=False):
        assert len(input.payload) == 24
        self.input = input
        self.num_lanes = num_lanes
        self.image_width = ControlSignal(16, reset=image_width * 3)
        self.debug = debug

        self.vbp = ControlSignal(16, reset=18)
        self.vfp = ControlSignal(16, reset=4)
        self.hbp = ControlSignal(16, reset=68 * 3)
        self.hfp = ControlSignal(16, reset=20 * 3)

        self.gearbox_not_ready = StatusSignal(32)

        self.output = PacketizedStream(num_lanes * 8)

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

        gearbox = m.submodules.gearbox = StreamGearbox(
            self.input, target_width=len(self.output.payload))

        def repack_to_lanes(packet):
            values = [
                packet[i * 8:(i + 1) * 8] for i in range(len(packet) // 8)
            ]
            if self.num_lanes == 1:
                pass
            elif self.num_lanes == 2:
                values = [
                    Cat(values[0:2]),
                    Cat(values[2:4]),
                ]
            elif self.num_lanes == 4:
                values = Cat(values)
            else:
                raise AssertionError("Invalid number of lanes!")
            return [(v, i == len(values) - 1) for i, v in enumerate(values)]

        def short_packet_words(type, payload=Const(0, 16)):
            data_id = DataIdentifier(data_type=type,
                                     virtual_channel_identifier=0)
            packet_prelim = PacketHeader(data_id=data_id,
                                         word_count=payload,
                                         ecc=0)
            packet = PacketHeader(data_id=data_id,
                                  word_count=payload,
                                  ecc=packet_prelim.calculate_ecc())
            return repack_to_lanes(packet.as_value())

        def send_short_packet(p, type, payload=Const(0, 16), lp_after=False):
            for value, last in short_packet_words(type, payload):
                p += process_write_to_stream(m,
                                             self.output,
                                             payload=value,
                                             last=last & lp_after)

        def end_of_transmission(p):
            send_short_packet(
                p,
                DsiShortPacketDataType.END_OF_TRANSMISSION_PACKET,
                lp_after=True)

        blanking_counter = Signal(16)
        is_ready = Signal()

        def blanking(p,
                     length,
                     omit_footer=False,
                     type=DsiLongPacketDataType.BLANKING_PACKET_NO_DATA):
            length_without_overhead = length - 6
            m.d.sync += blanking_counter.eq(0)
            if not isinstance(length_without_overhead, Value):
                length_without_overhead = Const(length_without_overhead, 16)
            send_short_packet(p, type, length_without_overhead[0:16])
            with process_write_to_stream(m, self.output, payload=0x0):
                m.d.sync += blanking_counter.eq(blanking_counter + 1)
                m.d.comb += is_ready.eq(1)
            p += m.If(
                blanking_counter + is_ready >= length_without_overhead //
                2)  # the packet overhead is 6 bytes (4 header and 2 footer)
            if not omit_footer:
                p += process_write_to_stream(m, self.output,
                                             payload=0x0)  # checksum

        frame_last = Signal()
        v_porch_counter = Signal(16)

        def v_porch(name, to, length, skip_first_hsync=False):
            if not skip_first_hsync:
                first_name = name
                second_process_name = f"{name}_OVERHEAD"
            else:
                first_name = f"{name}_HSYNC"
                second_process_name = name

            with Process(m, first_name, to=second_process_name) as p:
                send_short_packet(p, DsiShortPacketDataType.H_SYNC_START)

            with Process(m, second_process_name, to=None) as p:
                blanking(p,
                         self.hfp + self.image_width + self.hbp,
                         type=DsiLongPacketDataType.NULL_PACKET_NO_DATA,
                         omit_footer=True)
                with process_write_to_stream(m, self.output, payload=0x0):
                    with m.If(v_porch_counter < length):
                        m.d.sync += v_porch_counter.eq(v_porch_counter + 1)
                        m.next = first_name
                    with m.Else():
                        m.d.sync += v_porch_counter.eq(0)
                        m.next = to

        trig = Signal()
        if self.debug and False:
            probe(m, self.output.valid)
            probe(m, self.output.ready)
            probe(m, self.output.last)
            probe(m, self.output.payload)
            probe(m, gearbox.output.ready)
            probe(m, gearbox.output.valid)
            probe(m, gearbox.output.frame_last)
            probe(m, gearbox.output.line_last)
            probe(m, gearbox.output.payload)

            # trigger(m, trig)

        with m.FSM() as fsm:
            fsm_status_reg(platform, m, fsm)
            if self.debug:
                ...
                #fsm_probe(m, fsm)

            with Process(m, "VSYNC_START", to="VBP") as p:
                send_short_packet(p, DsiShortPacketDataType.V_SYNC_START)

            v_porch("VBP", "LINE_START", self.vbp, skip_first_hsync=True)

            with Process(m, "LINE_START", to="LINE_DATA") as p:
                end_of_transmission(p)
                p += m.If(gearbox.output.valid)
                m.d.comb += trig.eq(1)
                send_short_packet(p, DsiShortPacketDataType.H_SYNC_START)
                blanking(p, self.hbp)
                send_short_packet(
                    p,
                    DsiLongPacketDataType.PACKED_PIXEL_STREAM_24_BIT_RGB_8_8_8,
                    self.image_width)
            with m.State("LINE_DATA"):
                with m.If(gearbox.output.line_last & gearbox.output.valid
                          & gearbox.output.ready):
                    m.next = "LINE_END"
                    m.d.sync += frame_last.eq(gearbox.output.frame_last)
                with m.If(~gearbox.output.valid):
                    m.d.sync += self.gearbox_not_ready.eq(
                        self.gearbox_not_ready + 1)
                m.d.comb += self.output.connect_upstream(gearbox.output,
                                                         allow_partial=True)
            with Process(m, "LINE_END", to=None) as p:
                p += process_write_to_stream(
                    m, self.output,
                    payload=0x0)  # TODO: handle the non 2 lane case
                blanking(
                    p, self.hfp, omit_footer=True
                )  # we omit the footer to be able to do dispatch the next state with zero cycle delay
                with process_write_to_stream(m, self.output, payload=0x0):
                    with m.If(frame_last):
                        m.next = "VFP"
                    with m.Else():
                        m.next = "LINE_START"

            v_porch("VFP", "FRAME_END", self.vfp)

            with Process(m, "FRAME_END", to="VSYNC_START") as p:
                end_of_transmission(p)

        return m