diff --git a/src/gateware/cxp_pipeline.py b/src/gateware/cxp_pipeline.py new file mode 100644 index 0000000..b9aebb3 --- /dev/null +++ b/src/gateware/cxp_pipeline.py @@ -0,0 +1,508 @@ +from migen import * +from misoc.interconnect.csr import * +from misoc.interconnect import stream +from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine, LiteEthMACCRCChecker + + +import struct + +upconn_dw = 8 +upconn_layout = [("data", upconn_dw), ("k", upconn_dw//8)] + +downconn_dw = 32 +downconn_layout = [("data", downconn_dw), ("k", downconn_dw//8)] + + +def K(x, y): + return ((y << 5) | x) + +KCode = { + "pak_start" : K(27, 7), + "io_ack" : K(28, 6), + "trig_indic_28_2" : K(28, 2), + "trig_indic_28_4" : K(28, 4), + "pak_end" : K(29, 7), +} + + +def _bytes2word(bytes, big_endian=True): + if big_endian: + return struct.unpack(">I", struct.pack(">4B", *bytes))[0] + else: + return struct.unpack("4B", *bytes))[0] + +class Code_Source(Module): + def __init__(self, layout, data, k): + + self.source = stream.Endpoint(layout) + self.stb = Signal() + + # # # + assert len(data) == len(k) > 0 + counts = len(data) + + cnt = Signal() if counts == 1 else Signal(max=counts) + clr_cnt = Signal() + inc_cnt = Signal() + + self.sync += [ + If(clr_cnt, + cnt.eq(cnt.reset), + ).Elif(inc_cnt, + cnt.eq(cnt + 1), + ) + ] + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + + fsm.act("IDLE", + clr_cnt.eq(1), + If(self.stb, + NextState("WRITE") + ) + ) + + fsm.act("WRITE", + self.source.stb.eq(1), + self.source.data.eq(Array(data)[cnt]), + self.source.k.eq(Array(k)[cnt]), + If(cnt == counts - 1, + self.source.eop.eq(1), + If(self.source.ack, NextState("IDLE")) + ).Else( + inc_cnt.eq(self.source.ack) + ) + ) + + +class Code_Inserter(Module): + def __init__(self, layout, data, k, insert_infront=True): + self.sink = stream.Endpoint(layout) + self.source = stream.Endpoint(layout) + + self.data = Signal.like(self.sink.data) + self.k = Signal.like(self.sink.k) + + # # # + assert len(data) == len(k) > 0 + counts = len(data) + + cnt = Signal() if counts == 1 else Signal(max=counts) + clr_cnt = Signal() + inc_cnt = Signal() + + self.sync += [ + If(clr_cnt, + cnt.eq(cnt.reset), + ).Elif(inc_cnt, + cnt.eq(cnt + 1), + ) + ] + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + remove_sink_oep = 0 if insert_infront else 1 + + # add code in front: IDLE -> INSERT -> COPY + # add code at end: IDLE -> COPY -> INSERT + fsm.act("IDLE", + self.sink.ack.eq(1), + clr_cnt.eq(1), + If(self.sink.stb, + self.sink.ack.eq(0), + NextState("INSERT" if insert_infront else "COPY"), + ) + ) + + fsm.act("INSERT", + self.sink.ack.eq(0), + self.source.stb.eq(1), + self.source.data.eq(Array(data)[cnt]), + self.source.k.eq(Array(k)[cnt]), + If(cnt == counts - 1, + If(remove_sink_oep, self.source.eop.eq(1)), + If(self.source.ack, NextState("COPY" if insert_infront else "IDLE")) + ).Else( + inc_cnt.eq(self.source.ack) + ) + ) + + fsm.act("COPY", + self.sink.connect(self.source), + If(remove_sink_oep, self.source.eop.eq(0)), + If(self.sink.stb & self.sink.eop & self.source.ack, + NextState("IDLE" if insert_infront else "INSERT"), + ) + ) + + +class Packet_Wrapper(Module): + def __init__(self, layout): + self.submodules.pak_start = pak_start = Code_Inserter(layout, [KCode["pak_start"]]*4, [1]*4) + self.submodules.pak_end = pak_end = Code_Inserter(layout, [KCode["pak_end"]]*4, [1]*4, insert_infront=False) + + self.comb += pak_start.source.connect(pak_end.sink), + self.sink = pak_start.sink + self.source = pak_end.source + +@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 += [ + self.engine.data.eq(self.data), + self.engine.last.eq(reg), + + self.value.eq(reg[::-1]), + self.error.eq(self.engine.next != self.check) + ] + +class CXPCRC32Checker(LiteEthMACCRCChecker): + def __init__(self, layout): + LiteEthMACCRCChecker.__init__(self, CXPCRC32, layout) + +class TX_Trigger(Module, AutoCSR): + def __init__(self): + self.trig_stb = Signal() + self.delay = Signal(upconn_dw) + self.linktrig_mode = Signal(max=4) + + # # # + + # Table 15 & 16 (CXP-001-2021) + # Send [K28.2, K28.4, K28.4] or [K28.4, K28.2, K28.2] and 3x delay as trigger packet + + self.submodules.code_src = code_src = Code_Source(upconn_layout, [self.delay]*3, [0]*3) + self.comb += code_src.stb.eq(self.trig_stb), + + header = [Signal(8) for _ in range(3)] + self.comb += \ + If((self.linktrig_mode == 0) | (self.linktrig_mode == 2), + header[0].eq(KCode["trig_indic_28_2"]), + header[1].eq(KCode["trig_indic_28_4"]), + header[2].eq(KCode["trig_indic_28_4"]), + ).Else( + header[0].eq(KCode["trig_indic_28_4"]), + header[1].eq(KCode["trig_indic_28_2"]), + header[2].eq(KCode["trig_indic_28_2"]), + ) + + self.submodules.inserter = inserter = Code_Inserter(upconn_layout, header, [1]*3) + + self.comb += code_src.source.connect(inserter.sink) + self.source = inserter.source + +class Trigger_ACK(Module): + def __init__(self): + self.ack = Signal() + + # # # + + # Section 9.3.2 (CXP-001-2021) + # Send 4x K28.6 and 4x 0x01 as trigger packet ack + self.submodules.code_src = code_src = Code_Source(upconn_layout, [0x01]*4, [0]*4) + self.submodules.inserter = inserter = Code_Inserter(upconn_layout, [KCode["io_ack"]]*4, [1]*4) + self.comb += [ + code_src.stb.eq(self.ack), + code_src.source.connect(inserter.sink) + ] + + self.source = inserter.source + +class TX_Command_Packet(Module, AutoCSR): + # Section 12.1.2 (CXP-001-2021) + # Max control packet size is 128 bytes + def __init__(self, fifo_depth=128): + self.len = CSRStorage(log2_int(fifo_depth)) + self.data = CSR(upconn_dw) + self.writeable = CSRStatus() + + # # # + + self.submodules.fifo = fifo = stream.SyncFIFO(upconn_layout, fifo_depth) + self.submodules.pak_wrp = pak_wrp = Packet_Wrapper(upconn_layout) + self.source = pak_wrp.source + + self.comb += fifo.source.connect(pak_wrp.sink) + + cnt = Signal(log2_int(fifo_depth), reset=1) + self.sync += [ + self.writeable.status.eq(fifo.sink.ack), + If(fifo.sink.ack, fifo.sink.stb.eq(0)), + If(self.data.re, + fifo.sink.stb.eq(1), + fifo.sink.data.eq(self.data.r), + + fifo.sink.k.eq(0), + If(cnt == self.len.storage, + fifo.sink.eop.eq(1), + cnt.eq(cnt.reset), + ).Else( + fifo.sink.eop.eq(0), + cnt.eq(cnt + 1), + ), + ) + ] + +class TX_Test_Packet(Module, AutoCSR): + def __init__(self): + + self.stb = CSR() + self.busy = CSRStatus() + + # # # + + self.submodules.test_pattern_src = test_pattern_src = Code_Source(upconn_layout, [*range(0x100)]*16, [0]*0x100*16) + self.submodules.pak_type_inserter = pak_type_inserter = Code_Inserter(upconn_layout, [0x04]*4, [0]*4) + self.submodules.pak_wrp = pak_wrp = Packet_Wrapper(upconn_layout) + self.comb += [ + test_pattern_src.source.connect(pak_type_inserter.sink), + pak_type_inserter.source.connect(pak_wrp.sink), + ] + + self.source = pak_wrp.source + self.sync += [ + test_pattern_src.stb.eq(self.stb.re), + If(self.stb.re, + self.busy.status.eq(1), + ).Elif(self.source.eop & self.source.ack, + self.busy.status.eq(0) + ) + ] + +class RX_Debug_Buffer(Module,AutoCSR): + def __init__(self): + self.submodules.buf_out = buf_out = stream.SyncFIFO(downconn_layout, 128) + self.sink = buf_out.sink + + self.inc = CSR() + self.dout_pak = CSRStatus(downconn_dw) + self.kout_pak = CSRStatus(downconn_dw//8) + self.dout_valid = CSRStatus() + + self.sync += [ + # output + buf_out.source.ack.eq(self.inc.re), + self.dout_pak.status.eq(buf_out.source.data), + self.kout_pak.status.eq(buf_out.source.k), + self.dout_valid.status.eq(buf_out.source.stb), + ] + +class Receiver_Path(Module, AutoCSR): + def __init__(self): + self.trig_ack = Signal() + self.trig_clr = Signal() + + self.packet_type = Signal(8) + self.decoder_err = Signal() + self.decoder_err_clr = Signal() + + self.test_err = Signal() + self.test_err_clr = Signal() + + # # # + + + self.submodules.trig_ack_checker = trig_ack_checker = CXP_Trig_Ack_Checker() + self.submodules.packet_decoder = packet_decoder = CXP_Data_Packet_Decode() + + # Error are latched + self.sync += [ + If(trig_ack_checker.ack, + self.trig_ack.eq(1), + ).Elif(self.trig_clr, + self.trig_ack.eq(0), + ), + + If(packet_decoder.decode_err, + self.decoder_err.eq(1), + ).Elif(self.decoder_err_clr, + self.decoder_err.eq(0), + ), + + If(packet_decoder.test_err, + self.test_err.eq(1), + ).Elif(self.test_err_clr, + self.test_err.eq(0), + ) + ] + self.comb += [ + self.packet_type.eq(packet_decoder.packet_type), + + ] + + pipeline = [ trig_ack_checker, packet_decoder ] + + 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 + + + +class CXP_Data_Packet_Decode(Module): + def __init__(self): + self.sink = stream.Endpoint(downconn_layout) + # This is where data stream comes out + self.source = stream.Endpoint(downconn_layout) + + self.packet_type = Signal(8) + self.decode_err = Signal() + + self.buffer = Signal(40*downconn_dw) + self.test_err = Signal() + # # # + + # decoder -> priorities mux(normal packet vs trigger ack) -> data packet mux (control ack, data stream, heartbeat, testmode, (optional Genlcam event)) + type = { + "data_stream": 0x01, + "control_ack_no_tag": 0x03, + "test_packet": 0x04, + "control_ack_with_tag": 0x06, + "event_ack": 0x08, + "heartbeat": 0x09, + + "debug" : 0x02, + } + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + + fsm.act("IDLE", + self.sink.ack.eq(1), + # TODO: add error correction? + If((self.sink.stb & (self.sink.data == _bytes2word([KCode["pak_start"]]*4)) & (self.sink.k == 0b1111)), + NextState("DECODE"), + ) + ) + + # TODO: decode all packet type here + + cnt = Signal(max=0x100) + + fsm.act("DECODE", + self.sink.ack.eq(1), + If(self.sink.stb, + NextValue(self.packet_type, self.sink.data[:8]), + + Case(self.sink.data[:8],{ + type["data_stream"]: NextState("STREAMING"), + type["debug"]: NextState("STREAMING"), + type["test_packet"]: [ + NextValue(cnt, 0), + NextState("VERIFY_TEST_PATTERN"), + ], + "default": [ + self.decode_err.eq(1), + # wait till next valid packet + NextState("IDLE"), + ], + }), + ) + ) + + # Section 9.9.1 (CXP-001-2021) + # the received test data packet (0x00, 0x01 ... 0xFF) + # need to be compared against the local test sequence generator + fsm.act("VERIFY_TEST_PATTERN", + self.sink.ack.eq(1), + If(self.sink.stb, + If(((self.sink.data == _bytes2word([KCode["pak_end"]]*4)) & (self.sink.k == 0b1111)), + NextState("IDLE"), + ).Else( + If(((self.sink.data != Cat(cnt, cnt+1, cnt+2, cnt+3))), + self.test_err.eq(1), + ), + If(cnt == 0xFC, + NextValue(cnt, cnt.reset), + ).Else( + NextValue(cnt, cnt + 4) + ) + ) + ) + + ) + + fsm.act("STREAMING", + If((self.sink.stb & (self.sink.data == _bytes2word([KCode["pak_end"]]*4)) & (self.sink.k == 0b1111)), + # discard K29,7 + self.sink.ack.eq(1), + NextState("IDLE") + ).Else( + self.sink.connect(self.source), + ) + ) + # # input pipeline stage - determine packet length based on type + # self.sync += [ + # packet_start.eq((self.sink.data[0] == K(27, 7)) & (self.sink.k[0] == 1)), + # packet_end.eq((self.sink.data[0] == K(29, 7)) & (self.sink.k[0] == 1)), + + # If((self.sink.data[0] == K(27, 7)) & (self.sink.k[0] == 1), + # packet_buffer_load.eq(1), + # ), + + + # trig_ack.eq((self.sink.data[0] == K(28, 6)) & (self.sink.k[0] == 1)), + # If(trig_ack, + # self.trig_ack.eq(self.sink.data[0]), + # trig_ack.eq(0), + # ).Elif(packet_buffer_load, + # # TODO: add test packet counting + # Case(buffer_count, + # {i: buffer[i*downconn_dw:(i+1)*downconn_dw].eq(self.sink.data) + # for i in range(40)}), + # buffer_count.eq(buffer_count + 1), + + +class CXP_Trig_Ack_Checker(Module, AutoCSR): + def __init__(self): + self.sink = stream.Endpoint(downconn_layout) + self.source = stream.Endpoint(downconn_layout) + + self.ack = Signal() + + # # # + + 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("COPY"), + ) + ) + + fsm.act("COPY", + If((self.sink.stb & (self.sink.data == _bytes2word([KCode["io_ack"]]*4)) & (self.sink.k == 0b1111)), + # discard K28,6 + self.sink.ack.eq(1), + NextState("CHECK_ACK") + ).Else( + self.sink.connect(self.source), + ) + ) + + fsm.act("CHECK_ACK", + If(self.sink.stb, + NextState("IDLE"), + # discard the word after K28,6 + self.sink.ack.eq(1), + If(self.sink.data == _bytes2word([0x01]*4), + self.ack.eq(1), + ) + ) + )