Exemple #1
0
    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)
Exemple #2
0
    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()
Exemple #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
Exemple #4
0
    def test_long_fifo(self):
        platform = SimPlatform()

        input_stream = PacketizedStream(32)
        dut = LastWrapper(input_stream, lambda i: BufferedSyncStreamFIFO(i, 200), last_rle_bits=10)

        random.seed(0)
        test_packets = [
            [random.randint(0, 2**32) for _ in range(12)],
            [random.randint(0, 2**32) for _ in range(24)],
            [random.randint(0, 2**32) for _ in range(1)],
            [random.randint(0, 2**32) for _ in range(1000)],
            [random.randint(0, 2**32) for _ in range(1000)],
            [random.randint(0, 2 ** 32) for _ in range(12)],
            [random.randint(0, 2 ** 32) for _ in range(1000)],
        ]

        def writer_process():
            for packet in test_packets:
                yield from write_packet_to_stream(input_stream, packet)
        platform.add_process(writer_process, "sync")

        def reader_process():
            read_packets = []
            while len(read_packets) < len(test_packets):
                read = (yield from read_packet_from_stream(dut.output))
                read_packets.append(read)
                print([len(p) for p in read_packets])

            self.assertEqual(read_packets, test_packets)
        platform.add_process(reader_process, "sync")

        platform.add_sim_clock("sync", 100e6)
        platform.sim(dut)
Exemple #5
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 #6
0
    def test_simple_gearbox_384_to_64_last(self):
        input = PacketizedStream(32 * 12)
        dut = SimpleStreamGearbox(input, 64)

        payload_a = int("".join(reversed([f"{i:03x}" for i in range(32)])), 16)
        payload_b = int("".join(reversed([f"{i:03x}" for i in range(57, 57 + 32)])), 16)

        print(hex(payload_a))
        print(hex(payload_b))

        def writer():
            yield from write_to_stream(input, payload=payload_a, last=1)
            yield from write_to_stream(input, payload=payload_b, last=0)

        def reader():
            payload_aa = payload_a
            for i in range(32 * 12 // 64):
                self.assertEqual((yield from read_from_stream(dut.output, extract=("payload", "last"))), (payload_aa & 0xffff_ffff_ffff_ffff, 0 if i < 5 else 1))
                payload_aa = payload_aa >> 64

            payload_bb = payload_b
            for i in range(32 * 12 // 64):
                print(i)
                self.assertEqual((yield from read_from_stream(dut.output, extract=("payload", "last"))), (payload_bb & 0xffff_ffff_ffff_ffff, 0))
                payload_bb = payload_bb >> 64

        platform = SimPlatform()
        platform.add_sim_clock("sync", 100e6)
        platform.add_process(writer, "sync")
        platform.add_process(reader, "sync")
        platform.sim(dut)
Exemple #7
0
    def test_simple_gearbox_dont_loose_last_16_to_4(self):
        input = PacketizedStream(16)
        dut = SimpleStreamGearbox(input, 4)

        def writer():
            last_count_gold = 0
            for i in range(50):
                last = (i % 5 == 0)
                last_count_gold += last
                yield from write_to_stream(input, payload=0, last=(i % 5 == 0))
                if i % 3 == 0:
                    yield from do_nothing()
            self.assertEqual(last_count_gold, 10)

        def reader():
            last_count = 0
            for i in range(200):
                last_count += (yield from read_from_stream(dut.output, extract="last"))
                if i % 10 == 0:
                    yield from do_nothing()
            self.assertEqual(last_count, 10)

        platform = SimPlatform()
        platform.add_sim_clock("sync", 100e6)
        platform.add_process(writer, "sync")
        platform.add_process(reader, "sync")
        platform.sim(dut)
Exemple #8
0
    def test_hello_world(self):
        platform = SimPlatform()
        m = Module()

        address_stream = PacketizedStream(8)
        mem = Memory(width=32, depth=128, init=[i + 2 for i in range(128)])
        reader = m.submodules.reader = StreamMemoryReader(address_stream, mem)

        def write_process():
            for i in range(128):
                yield from write_to_stream(address_stream,
                                           payload=i,
                                           last=(i % 8) == 0)
            yield Passive()

        def read_process():
            for i in range(128):
                data, last = (yield from
                              read_from_stream(reader.output,
                                               extract=("payload", "last")))
                assert data == i + 2
                assert last == ((i % 8) == 0)
            yield Passive()

        platform.add_sim_clock("sync", 100e6)
        platform.add_process(write_process, "sync")
        platform.sim(m, read_process)
Exemple #9
0
 def test_core_output_stream_contract(self):
     input_stream = PacketizedStream(32)
     device_input_stream: BasicStream
     def core_producer(i):
         nonlocal device_input_stream
         device_input_stream = i
         return LegalStreamSource(i.clone())
     dut = GenericMetadataWrapper(input_stream, core_producer)
     verify_stream_output_contract(dut, stream_output=device_input_stream, support_modules=(LegalStreamSource(input_stream),))
Exemple #10
0
    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)
Exemple #11
0
    def __init__(self,
                 input: PacketizedStream,
                 core_producer,
                 last_fifo_depth=3,
                 last_rle_bits=10):
        self.last_fifo_depth = last_fifo_depth
        self.last_rle_bits = last_rle_bits
        assert hasattr(input, "last")
        self.input = input

        self.core_input = BasicStream(input.payload.shape())
        self.core = core_producer(self.core_input)
        self.core_output = self.core.output

        self.error = StatusSignal(32)

        self.output = PacketizedStream(len(self.core_output.payload))
Exemple #12
0
    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())
Exemple #13
0
    def test_gearbox_8_to_4_last(self):
        input = PacketizedStream(8)
        dut = StreamGearbox(input, 4)

        def writer():
            yield from write_to_stream(input, payload=0b00_10_00_01, last=1)
            yield from write_to_stream(input, payload=0b10_00_01_00, last=0)

        def reader():
            self.assertEqual((yield from read_from_stream(dut.output, extract=("payload", "last"))), (0b0001, 0))
            self.assertEqual((yield from read_from_stream(dut.output, extract=("payload", "last"))), (0b0010, 1))
            self.assertEqual((yield from read_from_stream(dut.output, extract=("payload", "last"))), (0b0100, 0))
            self.assertEqual((yield from read_from_stream(dut.output, extract=("payload", "last"))), (0b1000, 0))

        platform = SimPlatform()
        platform.add_sim_clock("sync", 100e6)
        platform.add_process(writer, "sync")
        platform.add_process(reader, "sync")
        platform.sim(dut)
Exemple #14
0
    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)
Exemple #15
0
    def test_hello_world(self):
        platform = SimPlatform()
        m = Module()

        input = PacketizedStream(8)
        input_data = "hello, world :)"
        distribution = defaultdict(lambda: 0)
        for c in input_data:
            distribution[ord(c)] += 1
        huffman = m.submodules.huffman = HuffmanEncoder(input, distribution)

        def write_process():
            for i, c in enumerate(input_data):
                yield from write_to_stream(input,
                                           payload=ord(c),
                                           last=(i == len(input_data) - 1))

        def read_process():
            read = ""
            while True:
                data, length, last = (yield from read_from_stream(
                    huffman.output,
                    extract=("payload", "current_width", "last")))
                bitstring = "{:0255b}".format(data)[::-1][:length]
                read += bitstring
                if last:
                    break
            print(read)
            decode_iter = bitarray(read).iterdecode(
                {k: bitarray(v[::-1])
                 for k, v in huffman.table.items()})
            decoded = ""
            try:
                for c in decode_iter:
                    decoded += chr(c)
            except ValueError:  # Decoding may not finish with the byte boundary
                pass
            self.assertEqual(input_data, decoded)

        platform.add_sim_clock("sync", 100e6)
        platform.add_process(write_process, "sync")
        platform.sim(m, read_process)
Exemple #16
0
    def test_hello_world_bit_stuffing(self):
        platform = SimPlatform()
        m = Module()

        input = PacketizedStream(8)
        input_data = "hello, world :)"
        distribution = defaultdict(lambda: 0)
        for c in input_data:
            distribution[ord(c)] += 1
        huffman = m.submodules.huffman = HuffmanEncoder(input, distribution)
        bit_stuffing = m.submodules.bit_stuffing = BitStuffer(
            huffman.output, 8)

        def write_process():
            for i, c in enumerate(input_data):
                yield from write_to_stream(input,
                                           payload=ord(c),
                                           last=(i == len(input_data) - 1))

        def read_process():
            read = []
            while True:
                payload, last = (yield from
                                 read_from_stream(bit_stuffing.output,
                                                  extract=("payload", "last")))
                read.append("{:08b}".format(payload))
                if last:
                    break
            read_bitarray = "".join(x[::-1] for x in read)
            print(read_bitarray)
            decode_iter = bitarray(read_bitarray).iterdecode(
                {k: bitarray(v[::-1])
                 for k, v in huffman.table.items()})
            for c, expected in zip(decode_iter, input_data):
                self.assertEqual(chr(c), expected)

        platform.add_sim_clock("sync", 100e6)
        platform.add_process(write_process, "sync")
        platform.sim(m, read_process)
Exemple #17
0
    def test_PacketizedStream2ImageStream(self):
        platform = SimPlatform()
        input_stream = PacketizedStream(32)
        dut = PacketizedStream2ImageStream(input_stream, width=10)

        def write_process():
            for frame in range(10):
                yield from write_packet_to_stream(input_stream,
                                                  [0 for _ in range(100)])

        platform.add_process(write_process, "sync")

        def read_process():
            for frame in range(10):
                frame = yield from read_frame_from_stream(dut.output)
                self.assertEqual(len(frame), 10)
                self.assertTrue(all(len(l) == 10 for l in frame))

        platform.add_process(read_process, "sync")

        platform.add_sim_clock("sync", 100e6)
        platform.sim(dut)
Exemple #18
0
        def test_gearbox(input_width, output_width):
            input = PacketizedStream(input_width)
            m = Module()
            fifo_in = m.submodules.fifo_in = BufferedSyncStreamFIFO(input, 100)
            gearbox = m.submodules.gearbox = StreamGearbox(fifo_in.output, output_width)
            fifo_out = m.submodules.fifo_out = BufferedSyncStreamFIFO(gearbox.output, 100)

            input_data, output_data = gold_gen(input_width, output_width)

            def writer():
                for v in input_data:
                    yield from write_to_stream(input, payload=v)

            def reader():
                for i, v in enumerate(output_data):
                    read = (yield from read_from_stream(fifo_out.output))
                    self.assertEqual(read, v)

            platform = SimPlatform()
            platform.add_sim_clock("sync", 100e6)
            platform.add_process(writer, "sync")
            platform.add_process(reader, "sync")
            platform.sim(m)
Exemple #19
0
 def __init__(self, input: ImageStream):
     self.input = input
     self.output = PacketizedStream(self.input.payload.shape(),
                                    name="packetized_image_stream")
Exemple #20
0
 def test_output_stream_properties(self):
     input = PacketizedStream(8)
     verify_stream_output_contract(
         HuffmanEncoder(input, {i: i
                                for i in range(256)}),
         support_modules=(LegalStreamSource(input), ))
Exemple #21
0
 def test_output_stream_contract(self):
     input_stream = PacketizedStream(32)
     dut = GenericMetadataWrapper(input_stream, lambda i: BufferedSyncStreamFIFO(i, 10))
     verify_stream_output_contract(dut, support_modules=(LegalStreamSource(input_stream),))
Exemple #22
0
 def test_output_stream_contract(self):
     input_stream = PacketizedStream(32)
     dut = LastWrapper(input_stream, lambda i: BufferedSyncStreamFIFO(i, 10), last_rle_bits=3)
     verify_stream_output_contract(dut, support_modules=(LegalStreamSource(input_stream),))
Exemple #23
0
 def test_last_wrapper_contract(self):
     dut = LastWrapperContract(LastWrapper(PacketizedStream(32), lambda i: BufferedSyncStreamFIFO(i, 10)))
     assert_formal(dut, mode="hybrid", depth=10)
Exemple #24
0
 def __init__(self, input: VariableWidthStream, output_width):
     self.input = input
     self.output = PacketizedStream(output_width, name="bit_stuffer_output")
Exemple #25
0
 def test_output_stream_contract(self):
     input_stream = PacketizedStream(8)
     mem = Memory(width=32, depth=128, init=[i + 2 for i in range(128)])
     dut = StreamMemoryReader(input_stream, mem)
     verify_stream_output_contract(
         dut, support_modules=(LegalStreamSource(input_stream), ))
Exemple #26
0
    def __init__(self, packet_length_stream: BasicStream,
                 data_stream: BasicStream):
        self.packet_length_stream = packet_length_stream
        self.data_stream = data_stream

        self.output = PacketizedStream(self.data_stream.payload.shape())
Exemple #27
0
    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