forked from M-Labs/artiq-zynq
Compare commits
3 Commits
465609b3f3
...
b4a75fd8c9
Author | SHA1 | Date |
---|---|---|
morgan | b4a75fd8c9 | |
morgan | fbb2089c34 | |
morgan | 3fd7d3d905 |
|
@ -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)
|
|
@ -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")
|
|
@ -58,7 +58,7 @@ class Receiver(Module):
|
||||||
|
|
||||||
data_valid = Signal()
|
data_valid = Signal()
|
||||||
self.sync.cxp_gtx_rx += [
|
self.sync.cxp_gtx_rx += [
|
||||||
data_valid.eq(gtx.comma_checker.rxfsm.ongoing("READY")),
|
data_valid.eq(gtx.comma_aligner.rxfsm.ongoing("READY")),
|
||||||
|
|
||||||
self.source.stb.eq(0),
|
self.source.stb.eq(0),
|
||||||
If(data_valid & self.source.ack & ~((gtx.decoders[0].d == 0xBC) & (gtx.decoders[0].k == 1)),
|
If(data_valid & self.source.ack & ~((gtx.decoders[0].d == 0xBC) & (gtx.decoders[0].k == 1)),
|
||||||
|
@ -218,9 +218,33 @@ class QPLL(Module, AutoCSR):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RX_Resetter(Module):
|
||||||
|
def __init__(self, reset_period=10_000_000):
|
||||||
|
self.rx_ready = Signal()
|
||||||
|
self.rx_reset = Signal()
|
||||||
|
|
||||||
|
# # #
|
||||||
|
|
||||||
|
# periodically reset rx until rx is connected and receiving valid data
|
||||||
|
# as after connecting RXP/RXN, the whole RX need to be reset
|
||||||
|
|
||||||
|
reset_counter = Signal(reset=reset_period-1, max=reset_period)
|
||||||
|
self.sync += [
|
||||||
|
self.rx_reset.eq(0),
|
||||||
|
If(~self.rx_ready,
|
||||||
|
If(reset_counter == 0,
|
||||||
|
reset_counter.eq(reset_counter.reset),
|
||||||
|
self.rx_reset.eq(1),
|
||||||
|
).Else(
|
||||||
|
reset_counter.eq(reset_counter - 1),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
# Warning: Xilinx transceivers are LSB first, and comma needs to be flipped
|
# Warning: Xilinx transceivers are LSB first, and comma needs to be flipped
|
||||||
# compared to the usual 8b10b binary representation.
|
# compared to the usual 8b10b binary representation.
|
||||||
class Comma_Checker(Module):
|
class Comma_Aligner(Module):
|
||||||
def __init__(self, comma, reset_period=10_000_000):
|
def __init__(self, comma, reset_period=10_000_000):
|
||||||
self.data = Signal(20)
|
self.data = Signal(20)
|
||||||
self.comma_aligned = Signal()
|
self.comma_aligned = Signal()
|
||||||
|
@ -229,28 +253,9 @@ class Comma_Checker(Module):
|
||||||
|
|
||||||
self.aligner_en = Signal()
|
self.aligner_en = Signal()
|
||||||
self.ready_sys = Signal()
|
self.ready_sys = Signal()
|
||||||
self.restart_sys = Signal()
|
|
||||||
|
|
||||||
# # #
|
# # #
|
||||||
|
|
||||||
|
|
||||||
# periodically reset rx until rx is connected and receiving valid data
|
|
||||||
# as after connecting RXP/RXN, the whole RX need to be reset
|
|
||||||
|
|
||||||
reset_counter = Signal(reset=reset_period-1, max=reset_period)
|
|
||||||
self.sync += [
|
|
||||||
self.restart_sys.eq(0),
|
|
||||||
If(~self.ready_sys,
|
|
||||||
If(reset_counter == 0,
|
|
||||||
reset_counter.eq(reset_counter.reset),
|
|
||||||
self.restart_sys.eq(1),
|
|
||||||
).Else(
|
|
||||||
reset_counter.eq(reset_counter - 1),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Data and comma checker
|
# Data and comma checker
|
||||||
# From UG476 (v1.12.1) p.228
|
# From UG476 (v1.12.1) p.228
|
||||||
# The built-in RXBYTEISALIGNED can be falsely asserted at linerate higher than 5Gbps
|
# The built-in RXBYTEISALIGNED can be falsely asserted at linerate higher than 5Gbps
|
||||||
|
@ -694,14 +699,29 @@ class GTX(Module):
|
||||||
Instance("BUFG", i_I=txpll_clkout, o_O=self.cd_cxp_gtx_tx.clk),
|
Instance("BUFG", i_I=txpll_clkout, o_O=self.cd_cxp_gtx_tx.clk),
|
||||||
AsyncResetSynchronizer(self.cd_cxp_gtx_tx, ~self.txpll_locked & ~tx_init.done)
|
AsyncResetSynchronizer(self.cd_cxp_gtx_tx, ~self.txpll_locked & ~tx_init.done)
|
||||||
]
|
]
|
||||||
|
self.comb += tx_init.restart.eq(self.tx_restart)
|
||||||
|
|
||||||
# RX clocking
|
# RX clocking
|
||||||
# the CDR matches the required frequency for RXUSRCLK, no need for PLL
|
# the CDR matches the required frequency for RXUSRCLK, no need for PLL
|
||||||
self.clock_domains.cd_cxp_gtx_rx = ClockDomain()
|
|
||||||
self.specials += [
|
# Slave Rx will use cxp_gtx_rx instead
|
||||||
Instance("BUFG", i_I=self.rxoutclk, o_O=self.cd_cxp_gtx_rx.clk),
|
if rx_mode == "single" or rx_mode == "master":
|
||||||
AsyncResetSynchronizer(self.cd_cxp_gtx_rx, ~rx_init.done)
|
self.clock_domains.cd_cxp_gtx_rx = ClockDomain()
|
||||||
]
|
self.specials += [
|
||||||
|
Instance("BUFG", i_I=self.rxoutclk, o_O=self.cd_cxp_gtx_rx.clk),
|
||||||
|
AsyncResetSynchronizer(self.cd_cxp_gtx_rx, ~rx_init.done)
|
||||||
|
]
|
||||||
|
self.submodules.rx_resetter = rx_resetter = RX_Resetter()
|
||||||
|
self.comb += [
|
||||||
|
rx_resetter.rx_ready.eq(self.rx_ready),
|
||||||
|
rx_init.restart.eq(self.rx_restart | rx_resetter.rx_reset),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
self.comb += rx_init.restart.eq(self.rx_restart),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 8b10b Encoder/Decoder
|
||||||
|
|
||||||
self.comb += [
|
self.comb += [
|
||||||
txdata.eq(Cat(self.encoder.output[0], self.encoder.output[1], self.encoder.output[2], self.encoder.output[3])),
|
txdata.eq(Cat(self.encoder.output[0], self.encoder.output[1], self.encoder.output[2], self.encoder.output[3])),
|
||||||
|
@ -711,16 +731,12 @@ class GTX(Module):
|
||||||
self.decoders[3].input.eq(rxdata[30:]),
|
self.decoders[3].input.eq(rxdata[30:]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
self.submodules.comma_aligner = comma_aligner = Comma_Aligner(0b0101111100)
|
||||||
self.submodules.comma_checker = comma_checker = Comma_Checker(0b0101111100)
|
|
||||||
self.comb += [
|
self.comb += [
|
||||||
comma_checker.data.eq(rxdata),
|
comma_aligner.data.eq(rxdata),
|
||||||
comma_checker.comma_aligned.eq(comma_aligned),
|
comma_aligner.comma_aligned.eq(comma_aligned),
|
||||||
comma_checker.comma_realigned.eq(comma_realigned),
|
comma_aligner.comma_realigned.eq(comma_realigned),
|
||||||
comma_checker.comma_det.eq(comma_det),
|
comma_aligner.comma_det.eq(comma_det),
|
||||||
comma_aligner_en.eq(comma_checker.aligner_en),
|
comma_aligner_en.eq(comma_aligner.aligner_en),
|
||||||
self.rx_ready.eq(comma_checker.ready_sys),
|
self.rx_ready.eq(comma_aligner.ready_sys),
|
||||||
|
|
||||||
rx_init.restart.eq(self.rx_restart | comma_checker.restart_sys),
|
|
||||||
tx_init.restart.eq(self.tx_restart),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,7 +2,6 @@ from migen import *
|
||||||
from migen.genlib.cdc import MultiReg
|
from migen.genlib.cdc import MultiReg
|
||||||
from misoc.interconnect.csr import *
|
from misoc.interconnect.csr import *
|
||||||
from misoc.interconnect import stream
|
from misoc.interconnect import stream
|
||||||
from misoc.cores.liteeth_mini.mac.crc import LiteEthMACCRCEngine, LiteEthMACCRCChecker
|
|
||||||
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
|
@ -291,7 +290,6 @@ class RX_Debug_Buffer(Module,AutoCSR):
|
||||||
class Duplicated_Char_Decoder(Module):
|
class Duplicated_Char_Decoder(Module):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sink = stream.Endpoint(word_layout)
|
self.sink = stream.Endpoint(word_layout)
|
||||||
self.buffer = stream.Endpoint(word_layout)
|
|
||||||
self.source = stream.Endpoint(word_layout_dchar)
|
self.source = stream.Endpoint(word_layout_dchar)
|
||||||
|
|
||||||
# # #
|
# # #
|
||||||
|
@ -314,22 +312,23 @@ class Duplicated_Char_Decoder(Module):
|
||||||
# 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.
|
# 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 without implementing the voting logic inside the decoder
|
# And any downstream modules can access the voting result without implementing the voting logic inside the decoder
|
||||||
|
|
||||||
|
# cycle 1 - buffer data & calculate intermediate result
|
||||||
|
buffer = stream.Endpoint(word_layout)
|
||||||
self.sync += [
|
self.sync += [
|
||||||
self.sink.ack.eq(self.buffer.ack),
|
If((~buffer.stb | buffer.ack),
|
||||||
self.buffer.stb.eq(self.sink.stb),
|
buffer.stb.eq(self.sink.stb),
|
||||||
If(self.sink.stb,
|
buffer.payload.eq(self.sink.payload),
|
||||||
self.buffer.data.eq(self.sink.data),
|
)
|
||||||
self.buffer.k.eq(self.sink.k),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
self.comb += self.sink.ack.eq(~buffer.stb | buffer.ack)
|
||||||
# cycle 1 - calculate ABC, ABD, ACD & BCD
|
|
||||||
|
# calculate ABC, ABD, ACD, BCD
|
||||||
char = [[self.sink.data[i*8:(i+1)*8], self.sink.k[i]] for i in range(4)]
|
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)]
|
voters = [Record([("data", 8), ("k", 1)]) for _ in range(4)]
|
||||||
|
|
||||||
for i, comb in enumerate(combinations(char, 3)):
|
for i, comb in enumerate(combinations(char, 3)):
|
||||||
self.sync += [
|
self.sync += [
|
||||||
If(self.sink.stb,
|
If((~buffer.stb | buffer.ack),
|
||||||
voters[i].data.eq(reduce(and_, [code[0] for code in comb])),
|
voters[i].data.eq(reduce(and_, [code[0] for code in comb])),
|
||||||
voters[i].k.eq(reduce(and_, [code[1] for code in comb])),
|
voters[i].k.eq(reduce(and_, [code[1] for code in comb])),
|
||||||
)
|
)
|
||||||
|
@ -338,15 +337,16 @@ class Duplicated_Char_Decoder(Module):
|
||||||
|
|
||||||
# cycle 2 - inject the voting result
|
# cycle 2 - inject the voting result
|
||||||
self.sync += [
|
self.sync += [
|
||||||
self.buffer.ack.eq(self.source.ack),
|
If((~self.source.stb | self.source.ack),
|
||||||
self.source.stb.eq(self.buffer.stb),
|
self.source.stb.eq(buffer.stb),
|
||||||
If(self.buffer.stb,
|
self.source.data.eq(buffer.data),
|
||||||
self.source.data.eq(self.buffer.data),
|
self.source.k.eq(buffer.k),
|
||||||
self.source.k.eq(self.buffer.k),
|
|
||||||
self.source.dchar.eq(Replicate(reduce(or_, [v.data for v in voters]), 4)),
|
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.source.dchar_k.eq(Replicate(reduce(or_, [v.k for v in voters]), 4)),
|
||||||
),
|
)
|
||||||
]
|
]
|
||||||
|
self.comb += buffer.ack.eq(~self.source.stb | self.source.ack)
|
||||||
|
|
||||||
|
|
||||||
@FullMemoryWE()
|
@FullMemoryWE()
|
||||||
class RX_Bootstrap(Module):
|
class RX_Bootstrap(Module):
|
||||||
|
@ -523,34 +523,3 @@ class Trigger_Ack_Checker(Module, AutoCSR):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@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)
|
|
||||||
|
|
Loading…
Reference in New Issue