From 9e3877fcefc6a11b4c878aa0ddb4ba7e70d9548b Mon Sep 17 00:00:00 2001 From: morgan Date: Fri, 1 Nov 2024 15:24:58 +0800 Subject: [PATCH] sim: prototyping frame decoder sim: add double buffer sim: add eop marker for crc checker in double buffer sim: add KCode, pak type & CRC generator sim: add Stream Dispatcher --- sim_pipeline.py | 393 ++++++++++++++++++++++++++++++++++++++++++++++++ sim_stream.py | 159 ++++++++++++++++++++ 2 files changed, 552 insertions(+) create mode 100644 sim_pipeline.py create mode 100644 sim_stream.py diff --git a/sim_pipeline.py b/sim_pipeline.py new file mode 100644 index 0000000..f326f57 --- /dev/null +++ b/sim_pipeline.py @@ -0,0 +1,393 @@ +from migen import * +from misoc.interconnect.csr import * +from misoc.interconnect import stream +from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine + +from src.gateware.cxp_pipeline import * + +class EOP_Marker(Module): + def __init__(self): + self.sink = stream.Endpoint(word_layout_dchar) + self.source = stream.Endpoint(word_layout_dchar) + + # # # + + last_stb = Signal() + self.sync += [ + If((~self.source.stb | self.source.ack), + self.source.stb.eq(self.sink.stb), + self.source.payload.eq(self.sink.payload), + ), + last_stb.eq(self.sink.stb), + ] + self.comb += [ + self.sink.ack.eq(~self.source.stb | self.source.ack), + self.source.eop.eq(~self.sink.stb & last_stb), + ] + +class Stream_MetaData_Extractor(Module): + def __init__(self): + self.sink = stream.Endpoint(word_layout_dchar) + self.source = stream.Endpoint(word_layout_dchar) + + # # # + + # GOAL: + # 0) accept four sinks?? + # 1) decode SPH, SPT + # 2) decode Image header, line break + # 3) verify the crc before phrasing image data downstream + + # HOW?? + # combine streams? + # phrase linedata to sys CD + # check stream data tag + # only need to support mono16 for now + + self.stream_id = Signal(char_width) + self.pak_tag = Signal(char_width) + self.stream_pak_size = Signal(char_width * 2) + + self.submodules.fsm = fsm = FSM(reset_state="WAIT_HEADER") + + fsm.act( + "WAIT_HEADER", + NextValue(self.stream_id, self.stream_id.reset), + NextValue(self.pak_tag, self.pak_tag.reset), + NextValue(self.stream_pak_size, self.stream_pak_size.reset), + self.sink.ack.eq(1), + If( + self.sink.stb, + NextValue(self.stream_id, self.sink.dchar), + NextState("GET_PAK_TAG"), + ), + ) + + fsm.act( + "GET_PAK_TAG", + If( + self.sink.stb, + self.sink.ack.eq(1), + NextValue(self.pak_tag, self.sink.dchar), + NextState("GET_PAK_SIZE_0"), + ), + ) + + fsm.act( + "GET_PAK_SIZE_0", + self.sink.ack.eq(1), + If( + self.sink.stb, + NextValue(self.stream_pak_size[8:], self.sink.dchar), + NextState("GET_PAK_SIZE_1"), + ), + ) + + fsm.act( + "GET_PAK_SIZE_1", + self.sink.ack.eq(1), + If( + self.sink.stb, + NextValue(self.stream_pak_size[:8], self.sink.dchar), + NextState("STORE_BUFFER"), + ), + ) + + fsm.act( + "STORE_BUFFER", + self.sink.connect(self.source), + # both serve the same function but using the pak size I can remove eop injecter and save 1 cycle + If(self.sink.stb, + NextValue(self.stream_pak_size, self.stream_pak_size - 1), + If(self.stream_pak_size == 1, + NextState("WAIT_HEADER"), + ) + ), + # If((self.sink.stb & self.sink.eop), + # NextState("WAIT_HEADER"), + # ) + ) + +class Frame_Decoder(Module): + def __init__(self): + self.sink = stream.Endpoint(word_layout_dchar) + self.source = stream.Endpoint(word_layout_dchar) + + # # # + + # TODO: decode Image header, line break + +class Pixel_Decoder(Module): + def __init__(self, pixel_format="mono16"): + assert pixel_format == "mono16" + + self.sink = stream.Endpoint(word_layout_dchar) + self.source = stream.Endpoint(word_layout_dchar) + + # # # + + # TODO: support mono16 for now? + +class Streams_Dispatcher(Module): + def __init__(self, downconns): + n_downconn = len(downconns) + self.submodules.mux = mux = stream.Multiplexer(word_layout_dchar, n_downconn) + + for i, c in enumerate(downconns): + # if i == 0: + self.comb += [ + # no backpressure + c.source.ack.eq(1), + c.source.connect(getattr(mux, "sink"+str(i))) + ] + + self.source = stream.Endpoint(word_layout_dchar) + + self.submodules.fsm = fsm = FSM(reset_state="WAIT_HEADER") + + # TODO: add different downstream + # stream_id = Signal() + # case = dict((i, mux.source.connect(b.sink)) for i, b in enumerate(buffers)) + fsm.act( + "WAIT_HEADER", + mux.source.connect(self.source), + If(mux.source.eop, + NextState("SWITCH_CONN"), + ), + ) + + read_mask = Signal(max=n_downconn) + self.comb += mux.sel.eq(read_mask) + fsm.act( + "SWITCH_CONN", + If(read_mask == n_downconn - 1, + NextValue(read_mask, read_mask.reset), + ).Else( + NextValue(read_mask, read_mask + 1), + ), + NextState("WAIT_HEADER"), + ) + + +def reverse_bytes(s): + assert len(s) % 8 == 0 + char = [s[i*8:(i+1)*8] for i in range(len(s)//8)] + return Cat(char[::-1]) + +@ResetInserter() +@CEInserter() +class CXPCRC32(Module): + # Section 9.2.2.2 (CXP-001-2021) + width = 32 + polynom = 0x04C11DB7 + seed = 2**width - 1 + check = 0x00000000 + + def __init__(self, data_width): + self.data = Signal(data_width) + self.value = Signal(self.width) + self.error = Signal() + + # # # + + self.submodules.engine = LiteEthMACCRCEngine( + data_width, self.width, self.polynom + ) + reg = Signal(self.width, reset=self.seed) + self.sync += reg.eq(self.engine.next) + self.comb += [ + # the CRC Engine use Big Endian, need to reverse the bytes + self.engine.data.eq(reverse_bytes(self.data)), + self.engine.last.eq(reg), + self.value.eq(reverse_bytes(reg[::-1])), + self.error.eq(reg != self.check), + ] + +class CXPCRC32Inserter(Module): + def __init__(self): + self.sink = stream.Endpoint(word_layout) + self.source = stream.Endpoint(word_layout) + + # # # + + self.submodules.crc = crc = CXPCRC32(word_dw) + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + + fsm.act("IDLE", + crc.reset.eq(1), + self.sink.ack.eq(1), + If(self.sink.stb, + self.sink.ack.eq(0), + NextState("COPY"), + ) + ) + fsm.act("COPY", + crc.ce.eq(self.sink.stb & self.source.ack), + crc.data.eq(self.sink.data), + self.sink.connect(self.source), + self.source.eop.eq(0), + If(self.sink.stb & self.sink.eop & self.source.ack, + NextState("INSERT"), + ) + ) + fsm.act("INSERT", + self.source.stb.eq(1), + self.source.eop.eq(1), + self.source.data.eq(crc.value), + If(self.source.ack, NextState("IDLE")) + ) + +class StreamPacket_Wrapper(Module): + def __init__(self): + self.sink = stream.Endpoint(word_layout) + self.source = stream.Endpoint(word_layout) + + # # # + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + + fsm.act("IDLE", + self.sink.ack.eq(1), + If(self.sink.stb, + self.sink.ack.eq(0), + NextState("INSERT_HEADER_0"), + ) + ) + + fsm.act("INSERT_HEADER_0", + self.sink.ack.eq(0), + self.source.stb.eq(1), + self.source.data.eq(Replicate(KCode["pak_start"], 4)), + self.source.k.eq(Replicate(1, 4)), + If(self.source.ack, NextState("INSERT_HEADER_1")), + ) + + fsm.act("INSERT_HEADER_1", + self.sink.ack.eq(0), + self.source.stb.eq(1), + self.source.data.eq(Replicate(C(0x01, char_width), 4)), + self.source.k.eq(Replicate(0, 4)), + If(self.source.ack, NextState("COPY")), + ) + + fsm.act("COPY", + self.sink.connect(self.source), + self.source.eop.eq(0), + If(self.sink.stb & self.sink.eop & self.source.ack, + NextState("INSERT_FOOTER"), + ), + ) + + fsm.act("INSERT_FOOTER", + self.sink.ack.eq(0), + self.source.stb.eq(1), + self.source.data.eq(Replicate(KCode["pak_end"], 4)), + self.source.k.eq(Replicate(1, 4)), + # Simulate RX don't have eop tagged + # self.source.eop.eq(1), + If(self.source.ack, NextState("IDLE")), + ) + +# With KCode & 0x01*4 +class StreamData_Generator(Module): + def __init__(self): + # should be big enough for all test + self.submodules.buffer = buffer = stream.SyncFIFO(word_layout, 32) + self.submodules.crc_inserter = crc_inserter = CXPCRC32Inserter() + self.submodules.wrapper = wrapper = StreamPacket_Wrapper() + + # # # + pipeline = [buffer, crc_inserter, wrapper] + for s, d in zip(pipeline, pipeline[1:]): + self.comb += s.source.connect(d.sink) + self.sink = pipeline[0].sink + self.source = pipeline[-1].source + +# For verifying crc in stream data packet +class Double_Stream_Buffer(Module): + # default size is 2 kBtyes - Section 9.5.2 (CXP-001-2021) + def __init__(self, size=16000): + # detect and tag end of packet for crc + # self.submodules.eop_marker = eop_marker = EOP_Marker() + # self.sink = eop_marker.sink + self.sink = stream.Endpoint(word_layout_dchar) + + self.submodules.crc = crc = CXPCRC32(word_dw) + self.comb += crc.data.eq(self.sink.data) + + self.submodules.fsm = fsm = FSM(reset_state="RESET") + + write_mask = Signal() + self.submodules.line_buffer0 = line_buffer0 = ResetInserter()(stream.SyncFIFO(word_layout_dchar, 2**bits_for(size//word_dw))) + self.submodules.line_buffer1 = line_buffer1 = ResetInserter()(stream.SyncFIFO(word_layout_dchar, 2**bits_for(size//word_dw))) + fsm.act("RESET", + Case(write_mask, + { + 0: line_buffer0.reset.eq(1), + 1: line_buffer1.reset.eq(1), + } + ), + crc.reset.eq(1), + NextState("CHECKING"), + ) + + fsm.act("CHECKING", + self.sink.ack.eq(1), + If(self.sink.stb, + crc.ce.eq(1), + If(self.sink.eop, + # discard the crc at the end + NextState("SWITCH_BUFFER") + ).Else( + If(write_mask == 0, + self.sink.connect(line_buffer0.sink), + ).Else( + self.sink.connect(line_buffer1.sink), + ), + ) + ) + ) + + # only valid data will be passed to downstream + fsm.act("SWITCH_BUFFER", + If(~crc.error, + NextValue(write_mask, ~write_mask), + ), + NextState("RESET"), + ) + + self.submodules.mux = mux = stream.Multiplexer(word_layout_dchar, 2) + self.comb += [ + line_buffer0.source.connect(mux.sink0), + line_buffer1.source.connect(mux.sink1), + mux.sel.eq(~write_mask), + ] + self.source = mux.source + + # DEBUG: + self.write = Signal() + self.error = Signal() + self.comb += [ + self.write.eq(write_mask), + self.error.eq(crc.error), + ] + + # # to add eop in the same cycle + # # the tricks relies on the fact source lags sink one cycle + # # but fsm .connect by default use combinational logic which the same cycle rising/falling edge check immpossible + # fsm.act("CHECKING", + # NextValue(self.source.payload.raw_bits(), 0), + # NextValue(self.source.stb, 0), + # If(self.sink.stb, + # crc.ce.eq(1), + # If(self.sink.eop, + # NextState("RESET") + # ).Else( + # NextValue(self.source.payload.raw_bits(), self.sink.payload.raw_bits()), + # NextValue(self.source.stb, 1), + # ) + # ) + # ) + + # last_eop = Signal() + # self.comb += self.source.eop.eq(~last_eop & self.sink.eop) diff --git a/sim_stream.py b/sim_stream.py new file mode 100644 index 0000000..06b9bdd --- /dev/null +++ b/sim_stream.py @@ -0,0 +1,159 @@ +from migen import * +from misoc.interconnect import stream +from sim_pipeline import * + +from src.gateware.cxp_pipeline import * + +class CXP_Links(Module): + def __init__(self): + # TODO: select the correct buffer to read from + # NOTE: although there are double buffer in each connect, the reading must be faster than writing to avoid data loss + + self.downconns = [] + for i in range(2): + downconn = Pipeline() + setattr(self.submodules, "cxp_conn"+str(i), downconn) + self.downconns.append(downconn) + + self.submodules.dispatcher = dispatcher = Streams_Dispatcher(self.downconns) + + # TODO: add extractor + # self.submodules.double_buffer = double_buffer = Double_Stream_Buffer() + + pipeline = [dispatcher] + for s, d in zip(pipeline, pipeline[1:]): + self.comb += s.source.connect(d.sink) + self.source = pipeline[-1].source + + # no backpressure + self.sync += self.source.ack.eq(1) + + +class Pipeline(Module): + def __init__(self): + self.submodules.generator = generator = StreamData_Generator() + self.submodules.dchar_decoder = dchar_decoder = Duplicated_Char_Decoder() + self.submodules.data_decoder = data_decoder = RX_Bootstrap() + self.submodules.eop_marker = eop_marker = EOP_Marker() + + # # # + + pipeline = [generator, dchar_decoder, data_decoder, eop_marker] + for s, d in zip(pipeline, pipeline[1:]): + self.comb += s.source.connect(d.sink) + self.sink = pipeline[0].sink + self.source = pipeline[-1].source + + # self.comb += self.source.ack.eq(1) + + +dut = CXP_Links() + +def check_case(packet=[]): + + + + print("=================TEST========================") + downconns = dut.downconns + ch = 0 + + for i, p in enumerate(packet): + for x in range(len(downconns)): + if x == ch: + yield downconns[x].sink.data.eq(p["data"]) + yield downconns[x].sink.k.eq(p["k"]) + yield downconns[x].sink.stb.eq(1) + else: + yield downconns[x].sink.data.eq(0) + yield downconns[x].sink.k.eq(0) + yield downconns[x].sink.stb.eq(0) + yield downconns[x].sink.eop.eq(0) + if "eop" in p: + yield downconns[ch].sink.eop.eq(1) + # compensate for delay + # yield + # yield downconns[ch].sink.data.eq(0) + # yield downconns[ch].sink.k.eq(0) + # yield downconns[ch].sink.stb.eq(0) + # yield downconns[ch].sink.eop.eq(0) + # yield + # yield + # yield + ch = (ch + 1) % len(downconns) + else: + yield downconns[ch].sink.eop.eq(0) + + # check cycle result + yield + source = dut.dispatcher.mux.source + print( + f"\nCYCLE#{i} : source char = {yield source.data:#X} k = {yield source.k:#X} stb = {yield source.stb} ack = {yield source.ack} eop = {yield source.eop}" + # f" source dchar = {yield source.dchar:#X} dchar_k = {yield source.dchar_k:#X}" + f"\nCYCLE#{i} : read mask = {yield dut.dispatcher.mux.sel}" + # f"\nCYCLE#{i} : stream id = {yield decoder.stream_id:#X} pak_tag = {yield decoder.pak_tag:#X}" + # f" stream_pak_size = {yield decoder.stream_pak_size:#X}" + ) + # crc = downconns[1].generator.crc_inserter.crc + # crc = dut.double_buffer.crc + # print( + # f"CYCLE#{i} : crc error = {yield crc.error:#X} crc value = {yield crc.value:#X}" + # f" crc data = {yield crc.data:#X} engine next = {yield crc.engine.next:#X} ce = {yield crc.ce}" + # ) + + + # extra clk cycles + cyc = i + 1 + for i in range(cyc, cyc + 30): + for x in range(len(downconns)): + # yield won't reset every cycle + yield downconns[x].sink.data.eq(0) + yield downconns[x].sink.k.eq(0) + yield downconns[x].sink.stb.eq(0) + yield downconns[x].sink.eop.eq(0) + yield + print( + f"\nCYCLE#{i} : source char = {yield source.data:#X} k = {yield source.k:#X} stb = {yield source.stb} ack = {yield source.ack} eop = {yield source.eop}" + # f" source dchar = {yield source.dchar:#X} dchar_k = {yield source.dchar_k:#X}" + f"\nCYCLE#{i} : read mask = {yield dut.dispatcher.mux.sel}" + # f"\nCYCLE#{i} : stream id = {yield decoder.stream_id:#X} pak_tag = {yield decoder.pak_tag:#X}" + # f" stream_pak_size = {yield decoder.stream_pak_size:#X}" + ) + assert True + + +def testbench(): + stream_id = 0x69 + streams = [ + [ + {"data": 0x11111111, "k": Replicate(0, 4)}, + {"data": 0xB105F00D, "k": Replicate(0, 4)}, + ], + [ + {"data": 0x22222222, "k": Replicate(0, 4)}, + {"data": 0xC00010FF, "k": Replicate(0, 4)}, + ], + [ + {"data": 0x33333333, "k": Replicate(0, 4)}, + {"data": 0xC0A79AE5, "k": Replicate(0, 4)}, + ], + ] + + packet = [] + for i, s in enumerate(streams): + s[-1]["eop"] = 0 + packet += [ + {"data": Replicate(C(stream_id, char_width), 4), "k": Replicate(0, 4)}, + {"data": Replicate(C(i, char_width), 4), "k": Replicate(0, 4)}, + { + "data": Replicate(C(len(s) >> 8 & 0xFF, char_width), 4), + "k": Replicate(0, 4), + }, + {"data": Replicate(C(len(s) & 0xFF, char_width), 4), "k": Replicate(0, 4)}, + *s, + ] + + + yield from check_case(packet) + + +run_simulation(dut, testbench(), vcd_name="sim-cxp.vcd")