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 duplicate char decoder
rx pipeline: add trig ack checker
rx pipeline: add packet decoder
decoder: add test packet checher
decoder: add packet DMA
This commit is contained in:
morgan 2024-08-29 17:39:07 +08:00
parent ec4a689e82
commit 35f054a990

View File

@ -0,0 +1,480 @@
from migen import *
from misoc.interconnect.csr import *
from misoc.interconnect.stream import Endpoint, SyncFIFO
from functools import reduce
from itertools import combinations
from operator import or_, and_
char_width = 8
char_layout = [("data", char_width), ("k", char_width//8)]
word_width = 32
word_layout = [("data", word_width), ("k", word_width//8)]
word_layout_dchar = [
("data", word_width),
("k", word_width//8),
("dchar", char_width),
("dchar_k", char_width//8),
]
def K(x, y):
return ((y << 5) | x)
def switch_endianness(s):
assert len(s) % 8 == 0
char = [s[i*8:(i+1)*8] for i in range(len(s)//8)]
return Cat(char[::-1])
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),
"stream_marker" : C(K(28, 3), 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 RX_Debug_Buffer(Module,AutoCSR):
def __init__(self, layout, size):
self.submodules.buf_out = buf_out = SyncFIFO(layout, size, True)
self.sink = buf_out.sink
self.inc = CSR()
self.dout_valid = CSRStatus()
self.dout_pak = CSRStatus(word_width)
self.kout_pak = CSRStatus(word_width//8)
self.crc_error = CSRStatus()
self.eop = CSRStatus()
self.sync += [
buf_out.source.ack.eq(self.inc.re),
self.dout_valid.status.eq(buf_out.source.stb),
# output
self.eop.status.eq(buf_out.source.eop),
self.dout_pak.status.eq(buf_out.source.data),
self.kout_pak.status.eq(buf_out.source.k),
# self.crc_error.status.eq(buf_out.source.error)
]
class Duplicated_Char_Decoder(Module):
def __init__(self):
self.sink = Endpoint(word_layout)
self.source = Endpoint(word_layout_dchar)
# # #
# For duplicated characters, an error correction method (e.g. majority voting) is required to meet the CXP spec:
# RX decoder should immune to single bit errors when handling duplicated characters - Section 9.2.2.1 (CXP-001-2021)
#
#
# 32
# +---> buffer -----/-----+
# 32 | | 32+8(dchar)
# sink ---/---+ ---> source -----/-----> down
# | 8(dchar) | decoders
# +---> majority -----/-----+
# voting
#
#
# Due to the tight setup/hold time requiremnt for 12.5Gbps CXP, the voting logic cannot be implemented as combinational logic
# Hence, a pipeline approach is needed to avoid any s/h violation, where the majority voting result are pre-calculate and injected into the bus immediate after the PHY.
# And any downstream modules can access the voting result anytime
# cycle 1 - buffer data & calculate intermediate result
buffer = Endpoint(word_layout)
self.sync += [
If(buffer.ack,
self.sink.connect(buffer, omit={"ack"}),
)
]
self.comb += self.sink.ack.eq(buffer.ack)
# calculate ABC, ABD, ACD, BCD
char = [[self.sink.data[i*8:(i+1)*8], self.sink.k[i]] for i in range(4)]
voters = [Record([("data", 8), ("k", 1)]) for _ in range(4)]
for i, comb in enumerate(combinations(char, 3)):
self.sync += [
If(buffer.ack,
voters[i].data.eq(reduce(and_, [code[0] for code in comb])),
voters[i].k.eq(reduce(and_, [code[1] for code in comb])),
)
]
# cycle 2 - inject the voting result
self.sync += [
If(self.source.ack,
buffer.connect(self.source, omit={"ack", "dchar", "dchar_k"}),
self.source.dchar.eq(Replicate(reduce(or_, [v.data for v in voters]), 4)),
self.source.dchar_k.eq(Replicate(reduce(or_, [v.k for v in voters]), 4)),
)
]
self.comb += buffer.ack.eq(self.source.ack)
class Packet_Arbiter(Module):
def __init__(self):
self.decode_err = Signal()
self.recv_test_pak = Signal()
self.recv_heartbeat = Signal()
self.sink = Endpoint(word_layout_dchar)
self.source_stream = Endpoint(word_layout_dchar)
self.source_test = Endpoint(word_layout_dchar)
self.source_heartbeat = Endpoint(word_layout_dchar)
self.source_command = Endpoint(word_layout_dchar)
# # #
type = {
"data_stream": 0x01,
"control_ack_no_tag": 0x03,
"test_packet": 0x04,
"control_ack_with_tag": 0x06,
"event": 0x07,
"heartbeat": 0x09,
}
# Data packet parser
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
fsm.act("IDLE",
self.sink.ack.eq(1),
If((self.sink.stb & (self.sink.dchar == KCode["pak_start"]) & (self.sink.dchar_k == 1)),
NextState("DECODE"),
)
)
fsm.act("DECODE",
self.sink.ack.eq(1),
If(self.sink.stb,
Case(self.sink.dchar, {
type["data_stream"]: NextState("COPY_STREAM_PACKET"),
type["test_packet"]: [
self.recv_test_pak.eq(1),
NextState("COPY_TEST_PACKET"),
],
type["control_ack_no_tag"]:[
# pass packet type for downstream decoder
self.source_command.stb.eq(1),
self.source_command.data.eq(self.sink.data),
NextState("COPY_COMMAND_PACKET"),
],
type["control_ack_with_tag"]:[
# pass packet type for downstream decoder
self.source_command.stb.eq(1),
self.source_command.data.eq(self.sink.data),
NextState("COPY_COMMAND_PACKET"),
],
type["event"]: [
# pass packet type for downstream decoder
self.source_command.stb.eq(1),
self.source_command.data.eq(self.sink.data),
NextState("COPY_COMMAND_PACKET"),
],
type["heartbeat"] : [
self.recv_heartbeat.eq(1),
NextState("COPY_HEARTBEAT_PACKET"),
],
"default": [
self.decode_err.eq(1),
# wait till next valid packet
NextState("IDLE"),
],
}),
)
)
# copy stream data packet with K29.7
fsm.act("COPY_STREAM_PACKET",
self.sink.connect(self.source_stream),
If((self.sink.stb & self.source_stream.ack & (self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
NextState("IDLE")
)
)
# copy test sequence packet with K29.7
fsm.act("COPY_TEST_PACKET",
self.sink.connect(self.source_test),
If((self.sink.stb & self.source_test.ack & (self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
NextState("IDLE")
)
)
# copy command packet with K29.7
fsm.act("COPY_COMMAND_PACKET",
self.sink.connect(self.source_command),
If((self.sink.stb & self.source_command.ack & (self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
NextState("IDLE")
)
)
# copy heartbeat packet with K29.7
fsm.act("COPY_HEARTBEAT_PACKET",
self.sink.connect(self.source_heartbeat),
If((self.sink.stb & self.source_heartbeat.ack & (self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
NextState("IDLE")
)
)
@FullMemoryWE()
class Command_Packet_Reader(Module):
def __init__(self, buffer_depth, nslot):
self.write_ptr = Signal(log2_int(nslot))
self.read_ptr = Signal.like(self.write_ptr)
self.buffer_err = Signal()
self.sink = Endpoint(word_layout_dchar)
# # #
# N buffers for firmware to read packet from
self.specials.mem = mem = Memory(word_width, nslot*buffer_depth)
self.specials.mem_port = mem_port = mem.get_port(write_capable=True)
buf_mem_we = Signal.like(mem_port.we)
buf_mem_dat_w = Signal.like(mem_port.dat_w)
buf_mem_adr = Signal.like(mem_port.adr)
# buffered mem_port to improve timing
self.sync += [
mem_port.we.eq(buf_mem_we),
mem_port.dat_w.eq(buf_mem_dat_w),
mem_port.adr.eq(buf_mem_adr)
]
addr_nbits = log2_int(buffer_depth)
addr = Signal(addr_nbits)
self.comb += [
buf_mem_adr[:addr_nbits].eq(addr),
buf_mem_adr[addr_nbits:].eq(self.write_ptr),
]
# Data packet parser
self.submodules.fsm = fsm = FSM(reset_state="LOAD_BUFFER")
fsm.act("LOAD_BUFFER",
buf_mem_we.eq(0),
self.sink.ack.eq(1),
If(self.sink.stb,
If(((self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
NextState("MOVE_BUFFER_PTR"),
).Else(
buf_mem_we.eq(1),
buf_mem_dat_w.eq(self.sink.data),
NextValue(addr, addr + 1),
If(addr == buffer_depth - 1,
# discard the packet
self.buffer_err.eq(1),
NextValue(addr, addr.reset),
)
)
)
)
fsm.act("MOVE_BUFFER_PTR",
self.sink.ack.eq(0),
If(self.write_ptr + 1 == self.read_ptr,
# if next one hasn't been read, overwrite the current buffer when new packet comes in
self.buffer_err.eq(1),
).Else(
NextValue(self.write_ptr, self.write_ptr + 1),
),
NextValue(addr, addr.reset),
NextState("LOAD_BUFFER"),
)
class Heartbeat_Packet_Reader(Module):
def __init__(self):
self.sink = Endpoint(word_layout_dchar)
self.host_id = Signal(4*char_width)
self.heartbeat = Signal(8*char_width)
# # #
n_chars = 12
packet_layout = [
("stream_id", len(self.host_id)),
("source_tag", len(self.heartbeat)),
]
assert layout_len(packet_layout) == n_chars*char_width
cnt = Signal(max=n_chars)
packet_buffer = Signal(layout_len(packet_layout))
case = dict(
(i, packet_buffer[8*i:8*(i+1)].eq(self.sink.dchar))
for i in range(n_chars)
)
self.sync += [
self.host_id.eq(switch_endianness(packet_buffer[:4*char_width])),
self.heartbeat.eq(switch_endianness(packet_buffer[4*char_width:])),
self.sink.ack.eq(1),
If(self.sink.stb,
Case(cnt, case),
If(((self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
cnt.eq(cnt.reset),
).Else(
cnt.eq(cnt + 1),
),
),
]
class Test_Sequence_Checker(Module):
def __init__(self):
self.error = Signal()
self.sink = Endpoint(word_layout_dchar)
# # #
# 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
cnt_bytes = [Signal(char_width, reset=i) for i in range(4)]
test_errors = [Signal() for _ in range(4)]
self.sync += [
self.sink.ack.eq(1),
self.error.eq(reduce(or_, test_errors))
]
for i, (cnt, err) in enumerate(zip(cnt_bytes,test_errors)):
self.sync += [
err.eq(0),
If(self.sink.stb,
If(((self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
cnt.eq(cnt.reset)
).Else(
If(self.sink.data[8 * i : 8 * (i + 1)] != cnt,
err.eq(1),
),
If(cnt == 0xFC + i,
cnt.eq(cnt.reset),
).Else(
cnt.eq(cnt + 4)
),
)
)
]
class Trigger_Reader(Module):
def __init__(self):
self.sink = Endpoint(word_layout_dchar)
self.source = Endpoint(word_layout_dchar)
self.trig = Signal()
self.delay = Signal(char_width)
self.linktrigger_n = Signal(char_width)
# # #
self.submodules.fsm = fsm = FSM(reset_state="COPY")
fsm.act("COPY",
If((self.sink.stb & (self.sink.dchar == KCode["trig_indic_28_2"]) & (self.sink.dchar_k == 1)),
# discard K28,2
self.sink.ack.eq(1),
NextState("READ_DELAY")
).Else(
self.sink.connect(self.source),
)
)
fsm.act("READ_DELAY",
self.sink.ack.eq(1),
If(self.sink.stb,
NextValue(self.delay, self.sink.dchar),
NextState("READ_LINKTRIGGER"),
)
)
fsm.act("READ_LINKTRIGGER",
self.sink.ack.eq(1),
If(self.sink.stb,
NextValue(self.linktrigger_n, self.sink.dchar),
self.trig.eq(1),
NextState("COPY"),
)
)
class Trigger_ACK_Reader(Module):
def __init__(self):
self.sink = Endpoint(word_layout_dchar)
self.source = Endpoint(word_layout_dchar)
self.ack = Signal()
# # #
self.submodules.fsm = fsm = FSM(reset_state="COPY")
fsm.act("COPY",
If((self.sink.stb & (self.sink.dchar == KCode["io_ack"]) & (self.sink.dchar_k == 1)),
# discard K28,6
self.sink.ack.eq(1),
NextState("READ_ACK")
).Else(
self.sink.connect(self.source),
)
)
fsm.act("READ_ACK",
self.sink.ack.eq(1),
If(self.sink.stb,
NextState("COPY"),
# discard the word after K28,6
If((self.sink.dchar == 0x01) & (self.sink.dchar_k == 0),
self.ack.eq(1),
)
)
)
class Buffer(Module):
def __init__(self, layout):
self.sink = Endpoint(layout)
self.source = Endpoint(layout)
# # #
self.sync += [
If(self.source.ack,
self.sink.connect(self.source, omit={"ack"}),
),
]
self.comb += [
self.sink.ack.eq(self.source.ack),
]
class EOP_Marker(Module):
def __init__(self):
self.sink = Endpoint(word_layout_dchar)
self.source = Endpoint(word_layout_dchar)
# # #
self.sync += [
If(self.source.ack,
If(~((self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1)),
self.sink.connect(self.source, omit={"ack", "eop"}),
).Else(
# don't pass K29.7 to downstream
self.source.stb.eq(0),
)
),
]
self.comb += [
self.sink.ack.eq(self.source.ack),
self.source.eop.eq(((self.sink.dchar == KCode["pak_end"]) & (self.sink.dchar_k == 1))),
]