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
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
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
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
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
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
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