From 12d8aed1d8bfd9334bdc874b99b3a659c24b2301 Mon Sep 17 00:00:00 2001 From: morgan Date: Thu, 29 Aug 2024 17:39:07 +0800 Subject: [PATCH] cxp pipeline: packet handling pipeline tx pipeline: add CRC32 inserter tx pipeline: add start & end of packet code inserter tx pipeline: add packet wrapper for start & stop packet indication tx pipeline: add code source for trigger & trigger ack packet tx pipeline: add packet for trigger & trigger ack tx pipeline: add test packet generator tx pipeline: add tx_command_packet for firmware tx command packet: add dma to store control packet rx pipeline: add reciever path rx pipeline: add trig ack checker rx pipeline: add packet decoder decoder: add test packet checher decoder: add packet DMA --- src/gateware/cxp_pipeline.py | 532 +++++++++++++++++++++++++++++++++++ 1 file changed, 532 insertions(+) create mode 100644 src/gateware/cxp_pipeline.py diff --git a/src/gateware/cxp_pipeline.py b/src/gateware/cxp_pipeline.py new file mode 100644 index 0000000..2e9dd39 --- /dev/null +++ b/src/gateware/cxp_pipeline.py @@ -0,0 +1,532 @@ +from migen import * +from migen.genlib.cdc import MultiReg +from misoc.interconnect.csr import * +from misoc.interconnect import stream +from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine, LiteEthMACCRCChecker + +char_width = 8 +char_layout = [("data", char_width), ("k", char_width//8)] + +word_dw = 32 +word_layout = [("data", word_dw), ("k", word_dw//8)] + +buffer_count = 4 +buffer_depth = 512 + +def K(x, y): + return ((y << 5) | x) + +KCode = { + "pak_start" : C(K(27, 7), char_width), + "io_ack" : C(K(28, 6), char_width), + "trig_indic_28_2" : C(K(28, 2), char_width), + "trig_indic_28_4" : C(K(28, 4), char_width), + "pak_end" : C(K(29, 7), char_width), + "idle_comma" : C(K(28, 5), char_width), + "idle_alignment" : C(K(28, 1), char_width), +} + +class Packet_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"), + ) + ) + + fsm.act("INSERT_HEADER", + self.sink.ack.eq(0), + self.source.stb.eq(1), + self.source.data.eq(Replicate(KCode["pak_start"], 4)), + self.source.k.eq(0b1111), + 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(0b1111), + self.source.eop.eq(1), + If(self.source.ack, NextState("IDLE")), + ) + +class TX_Trigger(Module): + def __init__(self): + self.stb = Signal() + self.delay = Signal(char_width) + self.linktrig_mode = Signal(max=4) + + # # # + + self.sink = stream.Endpoint(char_layout) + self.source = stream.Endpoint(char_layout) + + # 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 + + trig_packet = [Signal(char_width), Signal(char_width), Signal(char_width), self.delay, self.delay, self.delay] + trig_packet_k = [1, 1, 1, 0, 0, 0] + self.comb += [ + If((self.linktrig_mode == 0) | (self.linktrig_mode == 2), + trig_packet[0].eq(KCode["trig_indic_28_2"]), + trig_packet[1].eq(KCode["trig_indic_28_4"]), + trig_packet[2].eq(KCode["trig_indic_28_4"]), + ).Else( + trig_packet[0].eq(KCode["trig_indic_28_4"]), + trig_packet[1].eq(KCode["trig_indic_28_2"]), + trig_packet[2].eq(KCode["trig_indic_28_2"]), + ), + ] + + self.submodules.fsm = fsm = FSM(reset_state="COPY") + + cnt = Signal(max=6) + fsm.act("COPY", + NextValue(cnt, cnt.reset), + self.sink.connect(self.source), + If(self.stb, NextState("WRITE_TRIG")) + ) + + fsm.act("WRITE_TRIG", + self.sink.ack.eq(0), + self.source.stb.eq(1), + self.source.data.eq(Array(trig_packet)[cnt]), + self.source.k.eq(Array(trig_packet_k)[cnt]), + If(self.source.ack, + If(cnt == 5, + NextState("COPY"), + ).Else( + NextValue(cnt, cnt + 1), + ) + ) + ) + +class Idle_Word_Inserter(Module): + def __init__(self): + self.stb = Signal() + + # # # + + # Section 9.2.5 (CXP-001-2021) + # Send K28.5, K28.1, K28.1, D21.5 as idle word + self.submodules.fsm = fsm = FSM(reset_state="COPY") + + self.sink = stream.Endpoint(word_layout) + self.source = stream.Endpoint(word_layout) + fsm.act("COPY", + self.sink.connect(self.source), + If(self.stb, NextState("WRITE_IDLE")) + ) + + fsm.act("WRITE_IDLE", + self.sink.ack.eq(0), + self.source.stb.eq(1), + self.source.data.eq(Cat(KCode["idle_comma"], KCode["idle_alignment"], KCode["idle_alignment"], C(0xB5, char_width))), + self.source.k.eq(0b1110), + If(self.source.ack, NextState("COPY")), + ) + + +class Trigger_ACK_Inserter(Module): + def __init__(self): + self.stb = Signal() + + # # # + + # Section 9.3.2 (CXP-001-2021) + # Send 4x K28.6 and 4x 0x01 as trigger packet ack + self.submodules.fsm = fsm = FSM(reset_state="COPY") + + self.sink = stream.Endpoint(word_layout) + self.source = stream.Endpoint(word_layout) + fsm.act("COPY", + self.sink.connect(self.source), + If(self.stb, NextState("WRITE_ACK0")) + ) + + fsm.act("WRITE_ACK0", + self.sink.ack.eq(0), + self.source.stb.eq(1), + self.source.data.eq(Replicate(KCode["io_ack"], 4)), + self.source.k.eq(0b1111), + If(self.source.ack, NextState("WRITE_ACK1")), + ) + + fsm.act("WRITE_ACK1", + self.sink.ack.eq(0), + self.source.stb.eq(1), + self.source.data.eq(Replicate(C(0x01, char_width), 4)), + self.source.k.eq(0b0000), + If(self.source.ack, NextState("COPY")), + ) + + +@FullMemoryWE() +class TX_Command_Packet(Module, AutoCSR): + def __init__(self): + self.tx_word_len = CSRStorage(log2_int(buffer_depth)) + self.tx = CSR() + + # # # + + self.specials.mem = mem = Memory(word_dw, buffer_depth) + self.specials.mem_port = mem_port = mem.get_port() + self.source = stream.Endpoint(word_layout) + + + tx_done = Signal() + addr_next = Signal(log2_int(buffer_depth)) + addr = Signal.like(addr_next) + addr_rst = Signal() + addr_inc = Signal() + + # increment addr in the same cycle the moment addr_inc is high + # as memory takes one cycle to shift to the correct addr + self.sync += [ + addr.eq(addr_next), + If(self.tx.re, self.tx.w.eq(1)), + If(tx_done, self.tx.w.eq(0)), + ] + + self.comb += [ + addr_next.eq(addr), + If(addr_rst, + addr_next.eq(addr_next.reset), + ).Elif(addr_inc, + addr_next.eq(addr + 1), + ), + mem_port.adr.eq(addr_next), + self.source.data.eq(mem_port.dat_r) + ] + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + + fsm.act("IDLE", + addr_rst.eq(1), + If(self.tx.re, NextState("TRANSMIT")) + ) + fsm.act("TRANSMIT", + self.source.stb.eq(1), + If(self.source.ack, + addr_inc.eq(1), + ), + If(addr_next == self.tx_word_len.storage, + self.source.eop.eq(1), + tx_done.eq(1), + NextState("IDLE") + ) + ) + +class TX_Test_Packet(Module, AutoCSR): + def __init__(self): + self.tx = CSR() + + # # # + + tx_done = Signal() + self.sync += [ + If(self.tx.re, self.tx.w.eq(1)), + If(tx_done, self.tx.w.eq(0)), + ] + + self.source = stream.Endpoint(word_layout) + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + + cnt = Signal(max=0xFFF) + fsm.act("IDLE", + NextValue(cnt, cnt.reset), + If(self.tx.re, + NextState("WRITE_PACKET_TYPE") + ) + ) + + fsm.act("WRITE_PACKET_TYPE", + self.source.stb.eq(1), + self.source.data.eq(Replicate(C(0x04, char_width), 4)), + self.source.k.eq(0b0000), + If(self.source.ack,NextState("WRITE_TEST_COUNTER")) + ) + + fsm.act("WRITE_TEST_COUNTER", + self.source.stb.eq(1), + self.source.data.eq(Cat(cnt[:8], cnt[:8]+1, cnt[:8]+2, cnt[:8]+3)), + self.source.k.eq(0b0000), + If(self.source.ack, + If(cnt == 0xFFF-3, + tx_done.eq(1), + self.source.eop.eq(1), + NextState("IDLE") + ).Else( + NextValue(cnt, cnt + 4), + ) + + ) + ) + + + +class RX_Debug_Buffer(Module,AutoCSR): + def __init__(self): + self.submodules.buf_out = buf_out = stream.SyncFIFO(word_layout, 128) + self.sink = buf_out.sink + + self.inc = CSR() + self.dout_pak = CSRStatus(word_dw) + self.kout_pak = CSRStatus(word_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 Duplicate_Majority_Voter(Module): + def __init__(self, data, k): + assert data.nbits == 32 + assert k.nbits == 4 + + # Section 9.2.2.1 (CXP-001-2021) + # decoder should immune to single bit errors when handling duplicated characters + self.char = Signal(char_width) + self.k = Signal() + + a, a_k = data[:8], k[0] + b, b_k = data[8:16], k[1] + c, c_k = data[16:24], k[2] + d, d_k = data[24:], k[3] + self.comb += [ + self.char.eq(a&b&c | a&b&d | a&c&d | b&c&d), + self.k.eq(a_k&b_k&c_k | a_k&b_k&d_k | a_k&c_k&d_k | b_k&c_k&d_k), + ] + +@FullMemoryWE() +class CXP_Data_Packet_Decode(Module): + def __init__(self): + self.packet_type = Signal(8) + + self.decode_err = Signal() + self.test_err = Signal() + self.buffer_err = Signal() + # # # + + # TODO: heartbeat + type = { + "data_stream": 0x01, + "control_ack_no_tag": 0x03, + "test_packet": 0x04, + "control_ack_with_tag": 0x06, + "event": 0x07, + "heartbeat": 0x09, + } + + self.sink = stream.Endpoint(word_layout) + self.source = stream.Endpoint(word_layout) + + self.submodules.fsm = fsm = FSM(reset_state="IDLE") + self.submodules.voter = voter = Duplicate_Majority_Voter(self.sink.data, self.sink.k) + + fsm.act("IDLE", + self.sink.ack.eq(1), + If((self.sink.stb & (voter.char == KCode["pak_start"]) & (voter.k == 1)), + NextState("DECODE"), + ) + ) + + cnt = Signal(max=0x100) + addr_nbits = log2_int(buffer_depth) + addr = Signal(addr_nbits) + fsm.act("DECODE", + self.sink.ack.eq(1), + If(self.sink.stb, + NextValue(self.packet_type, voter.char), + + Case(voter.char, { + type["data_stream"]: NextState("STREAMING"), + type["test_packet"]: [ + NextValue(cnt, cnt.reset), + NextState("VERIFY_TEST_PATTERN"), + ], + type["control_ack_no_tag"]:[ + NextValue(addr, addr.reset), + NextState("LOAD_BUFFER"), + ], + type["control_ack_with_tag"]:[ + NextValue(addr, addr.reset), + NextState("LOAD_BUFFER"), + ], + type["event"]: [ + NextValue(addr, addr.reset), + NextState("LOAD_BUFFER"), + ], + "default": [ + self.decode_err.eq(1), + # wait till next valid packet + NextState("IDLE"), + ], + }), + ) + ) + # For stream data packet + fsm.act("STREAMING", + If((self.sink.stb & (voter.char == KCode["pak_end"]) & (voter.k == 1)), + # discard K29,7 + self.sink.ack.eq(1), + NextState("IDLE") + ).Else( + self.sink.connect(self.source), + ) + ) + + # 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(((voter.char == KCode["pak_end"]) & (voter.k == 1)), + 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) + ) + ) + ) + + ) + + # A circular buffer for firmware to read packet from + self.specials.mem = mem = Memory(word_dw, buffer_count*buffer_depth) + self.specials.mem_port = mem_port = mem.get_port(write_capable=True) + + write_ptr = Signal(log2_int(buffer_count)) + self.write_ptr_sys = Signal.like(write_ptr) + self.specials += MultiReg(write_ptr, self.write_ptr_sys), + + self.comb += [ + mem_port.adr[:addr_nbits].eq(addr), + mem_port.adr[addr_nbits:].eq(write_ptr), + ] + + # For control ack, event packet + fsm.act("LOAD_BUFFER", + mem_port.we.eq(0), + self.sink.ack.eq(1), + If(self.sink.stb, + If(((voter.char == KCode["pak_end"]) & (voter.k == 1)), + NextState("MOVE_BUFFER_PTR"), + ).Else( + mem_port.we.eq(1), + mem_port.dat_w.eq(self.sink.data), + NextValue(addr, addr + 1), + + If(addr == buffer_depth - 1, + # discard the packet + self.buffer_err.eq(1), + NextState("IDLE"), + ) + ) + ) + ) + + self.read_ptr_rx = Signal.like(write_ptr) + fsm.act("MOVE_BUFFER_PTR", + self.sink.ack.eq(0), + If(write_ptr + 1 == self.read_ptr_rx, + # if next one hasn't been read, overwrite the current buffer when new packet comes in + self.buffer_err.eq(1), + ).Else( + NextValue(write_ptr, write_ptr + 1), + ), + NextState("IDLE"), + ) + +class CXP_Trig_Ack_Checker(Module, AutoCSR): + def __init__(self): + self.sink = stream.Endpoint(word_layout) + self.source = stream.Endpoint(word_layout) + + self.ack = Signal() + + # # # + + self.submodules.fsm = fsm = FSM(reset_state="COPY") + + self.submodules.voter = voter = Duplicate_Majority_Voter(self.sink.data, self.sink.k) + + fsm.act("COPY", + If((self.sink.stb & (voter.char == KCode["io_ack"]) & (voter.k == 1)), + # 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("COPY"), + # discard the word after K28,6 + self.sink.ack.eq(1), + If((voter.char == 0x01) & (voter.k == 0), + self.ack.eq(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 += [ + self.engine.data.eq(self.data), + self.engine.last.eq(reg), + + self.value.eq(reg[::-1]), + self.error.eq(self.engine.next != self.check) + ] + +# For verifying crc in stream data packet +class CXPCRC32Checker(LiteEthMACCRCChecker): + def __init__(self, layout): + LiteEthMACCRCChecker.__init__(self, CXPCRC32, layout)