mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
380 lines
14 KiB
Python
380 lines
14 KiB
Python
# This file is Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr>
|
|
# This file is Copyright (c) 2019 Vamsi K Vytla <vkvytla@lbl.gov>
|
|
# License: BSD
|
|
|
|
from migen import *
|
|
from migen.genlib.roundrobin import *
|
|
from migen.genlib.record import *
|
|
from migen.genlib.fsm import FSM, NextState
|
|
|
|
from litex.gen import *
|
|
|
|
from litex.soc.interconnect import stream
|
|
|
|
# Status -------------------------------------------------------------------------------------------
|
|
|
|
class Status(Module):
|
|
def __init__(self, endpoint):
|
|
self.first = Signal(reset=1)
|
|
self.last = Signal()
|
|
self.ongoing = Signal()
|
|
|
|
ongoing = Signal()
|
|
self.comb += \
|
|
If(endpoint.valid,
|
|
self.last.eq(endpoint.last & endpoint.ready)
|
|
)
|
|
self.sync += ongoing.eq((endpoint.valid | ongoing) & ~self.last)
|
|
self.comb += self.ongoing.eq((endpoint.valid | ongoing) & ~self.last)
|
|
|
|
self.sync += [
|
|
If(self.last,
|
|
self.first.eq(1)
|
|
).Elif(endpoint.valid & endpoint.ready,
|
|
self.first.eq(0)
|
|
)
|
|
]
|
|
|
|
# Arbiter ------------------------------------------------------------------------------------------
|
|
|
|
class Arbiter(Module):
|
|
def __init__(self, masters, slave):
|
|
if len(masters) == 0:
|
|
pass
|
|
elif len(masters) == 1:
|
|
self.grant = Signal()
|
|
self.comb += masters.pop().connect(slave)
|
|
else:
|
|
self.submodules.rr = RoundRobin(len(masters))
|
|
self.grant = self.rr.grant
|
|
cases = {}
|
|
for i, master in enumerate(masters):
|
|
status = Status(master)
|
|
self.submodules += status
|
|
self.comb += self.rr.request[i].eq(status.ongoing)
|
|
cases[i] = [master.connect(slave)]
|
|
self.comb += Case(self.grant, cases)
|
|
|
|
# Dispatcher ---------------------------------------------------------------------------------------
|
|
|
|
class Dispatcher(Module):
|
|
def __init__(self, master, slaves, one_hot=False):
|
|
if len(slaves) == 0:
|
|
self.sel = Signal()
|
|
elif len(slaves) == 1:
|
|
self.comb += master.connect(slaves.pop())
|
|
self.sel = Signal()
|
|
else:
|
|
if one_hot:
|
|
self.sel = Signal(len(slaves))
|
|
else:
|
|
self.sel = Signal(max=len(slaves))
|
|
|
|
# # #
|
|
|
|
status = Status(master)
|
|
self.submodules += status
|
|
|
|
sel = Signal.like(self.sel)
|
|
sel_ongoing = Signal.like(self.sel)
|
|
self.sync += \
|
|
If(status.first,
|
|
sel_ongoing.eq(self.sel)
|
|
)
|
|
self.comb += \
|
|
If(status.first,
|
|
sel.eq(self.sel)
|
|
).Else(
|
|
sel.eq(sel_ongoing)
|
|
)
|
|
cases = {}
|
|
for i, slave in enumerate(slaves):
|
|
if one_hot:
|
|
idx = 2**i
|
|
else:
|
|
idx = i
|
|
cases[idx] = [master.connect(slave)]
|
|
cases["default"] = [master.ready.eq(1)]
|
|
self.comb += Case(sel, cases)
|
|
|
|
# Header -------------------------------------------------------------------------------------------
|
|
|
|
class HeaderField:
|
|
def __init__(self, byte, offset, width):
|
|
self.byte = byte
|
|
self.offset = offset
|
|
self.width = width
|
|
|
|
|
|
class Header:
|
|
def __init__(self, fields, length, swap_field_bytes=True):
|
|
self.fields = fields
|
|
self.length = length
|
|
self.swap_field_bytes = swap_field_bytes
|
|
|
|
def get_layout(self):
|
|
layout = []
|
|
for k, v in sorted(self.fields.items()):
|
|
layout.append((k, v.width))
|
|
return layout
|
|
|
|
def get_field(self, obj, name, width):
|
|
if "_lsb" in name:
|
|
field = getattr(obj, name.replace("_lsb", ""))[:width]
|
|
elif "_msb" in name:
|
|
field = getattr(obj, name.replace("_msb", ""))[width:2*width]
|
|
else:
|
|
field = getattr(obj, name)
|
|
if len(field) != width:
|
|
raise ValueError("Width mismatch on " + name + " field")
|
|
return field
|
|
|
|
def encode(self, obj, signal):
|
|
r = []
|
|
for k, v in sorted(self.fields.items()):
|
|
start = v.byte*8 + v.offset
|
|
end = start + v.width
|
|
field = self.get_field(obj, k, v.width)
|
|
if self.swap_field_bytes:
|
|
field = reverse_bytes(field)
|
|
r.append(signal[start:end].eq(field))
|
|
return r
|
|
|
|
def decode(self, signal, obj):
|
|
r = []
|
|
for k, v in sorted(self.fields.items()):
|
|
start = v.byte*8 + v.offset
|
|
end = start + v.width
|
|
field = self.get_field(obj, k, v.width)
|
|
if self.swap_field_bytes:
|
|
r.append(field.eq(reverse_bytes(signal[start:end])))
|
|
else:
|
|
r.append(field.eq(signal[start:end]))
|
|
return r
|
|
|
|
# Packetizer ---------------------------------------------------------------------------------------
|
|
|
|
class Packetizer(Module):
|
|
def __init__(self, sink_description, source_description, header):
|
|
self.sink = sink = stream.Endpoint(sink_description)
|
|
self.source = source = stream.Endpoint(source_description)
|
|
self.header = Signal(header.length*8)
|
|
|
|
# # #
|
|
|
|
# Parameters -------------------------------------------------------------------------------
|
|
data_width = len(self.sink.data)
|
|
bytes_per_clk = data_width//8
|
|
header_words = (header.length*8)//data_width
|
|
header_leftover = header.length%bytes_per_clk
|
|
|
|
# Signals ----------------------------------------------------------------------------------
|
|
sr = Signal(header.length*8, reset_less=True)
|
|
sr_load = Signal()
|
|
sr_shift = Signal()
|
|
count = Signal(max=max(header_words, 2))
|
|
sink_d = stream.Endpoint(sink_description)
|
|
|
|
# Header Encode/Load/Shift -----------------------------------------------------------------
|
|
self.comb += header.encode(sink, self.header)
|
|
self.sync += If(sr_load, sr.eq(self.header))
|
|
if header_words != 1:
|
|
self.sync += If(sr_shift, sr.eq(sr[data_width:]))
|
|
|
|
# FSM --------------------------------------------------------------------------------------
|
|
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
|
|
fsm_from_idle = Signal()
|
|
fsm.act("IDLE",
|
|
sink.ready.eq(1),
|
|
NextValue(count, 1),
|
|
If(sink.valid,
|
|
sink.ready.eq(0),
|
|
source.valid.eq(1),
|
|
source.last.eq(0),
|
|
source.data.eq(self.header[:data_width]),
|
|
If(source.valid & source.ready,
|
|
sr_load.eq(1),
|
|
NextValue(fsm_from_idle, 1),
|
|
If(header_words == 1,
|
|
If(header_leftover != 0,
|
|
NextState("UNALIGNED-DATA-COPY")
|
|
).Else(
|
|
NextState("ALIGNED-DATA-COPY")
|
|
)
|
|
).Else(
|
|
NextState("HEADER-SEND")
|
|
)
|
|
)
|
|
)
|
|
)
|
|
fsm.act("HEADER-SEND",
|
|
source.valid.eq(1),
|
|
source.last.eq(0),
|
|
source.data.eq(sr[min(data_width, len(sr)-1):]),
|
|
If(source.valid & source.ready,
|
|
sr_shift.eq(1),
|
|
If(count == (header_words - 1),
|
|
sr_shift.eq(0),
|
|
If(header_leftover,
|
|
NextState("UNALIGNED-DATA-COPY"),
|
|
NextValue(count, count + 1)
|
|
).Else(
|
|
NextState("ALIGNED-DATA-COPY")
|
|
)
|
|
).Else(
|
|
NextValue(count, count + 1),
|
|
)
|
|
)
|
|
)
|
|
fsm.act("ALIGNED-DATA-COPY",
|
|
source.valid.eq(sink.valid),
|
|
source.last.eq(sink.last),
|
|
source.data.eq(sink.data),
|
|
If(source.valid & source.ready,
|
|
sink.ready.eq(1),
|
|
If(source.last,
|
|
NextState("IDLE")
|
|
)
|
|
)
|
|
)
|
|
header_offset_multiplier = 1 if header_words == 1 else 2
|
|
self.sync += If(source.ready, sink_d.eq(sink))
|
|
fsm.act("UNALIGNED-DATA-COPY",
|
|
source.valid.eq(sink.valid | sink_d.last),
|
|
source.last.eq(sink_d.last),
|
|
If(fsm_from_idle,
|
|
source.data[:max(header_leftover*8, 1)].eq(sr[min(header_offset_multiplier*data_width, len(sr)-1):])
|
|
).Else(
|
|
source.data[:max(header_leftover*8, 1)].eq(sink_d.data[min((bytes_per_clk-header_leftover)*8, data_width-1):])
|
|
),
|
|
source.data[header_leftover*8:].eq(sink.data),
|
|
If(source.valid & source.ready,
|
|
sink.ready.eq(~source.last),
|
|
NextValue(fsm_from_idle, 0),
|
|
If(source.last,
|
|
NextState("IDLE")
|
|
)
|
|
)
|
|
)
|
|
|
|
# Error ------------------------------------------------------------------------------------
|
|
if hasattr(sink, "error") and hasattr(source, "error"):
|
|
self.comb += source.error.eq(sink.error)
|
|
|
|
# Last BE ----------------------------------------------------------------------------------
|
|
if hasattr(sink, "last_be") and hasattr(source, "last_be"):
|
|
rotate_by = header.length%bytes_per_clk
|
|
x = [sink.last_be[(i + rotate_by)%bytes_per_clk] for i in range(bytes_per_clk)]
|
|
self.comb += source.last_be.eq(Cat(*x))
|
|
|
|
# Depacketizer -------------------------------------------------------------------------------------
|
|
|
|
class Depacketizer(Module):
|
|
def __init__(self, sink_description, source_description, header):
|
|
self.sink = sink = stream.Endpoint(sink_description)
|
|
self.source = source = stream.Endpoint(source_description)
|
|
self.header = Signal(header.length*8)
|
|
|
|
# # #
|
|
|
|
# Parameters -------------------------------------------------------------------------------
|
|
data_width = len(sink.data)
|
|
bytes_per_clk = data_width//8
|
|
header_words = (header.length*8)//data_width
|
|
header_leftover = header.length%bytes_per_clk
|
|
|
|
# Signals ----------------------------------------------------------------------------------
|
|
sr = Signal(header.length*8, reset_less=True)
|
|
sr_shift = Signal()
|
|
sr_shift_leftover = Signal()
|
|
count = Signal(max=max(header_words, 2))
|
|
sink_d = stream.Endpoint(sink_description)
|
|
|
|
# Header Shift/Decode ----------------------------------------------------------------------
|
|
if (header_words) == 1 and (header_leftover == 0):
|
|
self.sync += If(sr_shift, sr.eq(sink.data))
|
|
else:
|
|
self.sync += [
|
|
If(sr_shift, sr.eq(Cat(sr[bytes_per_clk*8:], sink.data))),
|
|
If(sr_shift_leftover, sr.eq(Cat(sr[header_leftover*8:], sink.data)))
|
|
]
|
|
self.comb += self.header.eq(sr)
|
|
self.comb += header.decode(self.header, source)
|
|
|
|
# FSM --------------------------------------------------------------------------------------
|
|
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
|
|
fsm_from_idle = Signal()
|
|
fsm.act("IDLE",
|
|
sink.ready.eq(1),
|
|
NextValue(count, 1),
|
|
If(sink.valid,
|
|
sr_shift.eq(1),
|
|
NextValue(fsm_from_idle, 1),
|
|
If(header_words == 1,
|
|
If(header_leftover,
|
|
NextState("UNALIGNED-DATA-COPY")
|
|
).Else(
|
|
NextState("ALIGNED-DATA-COPY")
|
|
),
|
|
).Else(
|
|
NextState("HEADER-RECEIVE")
|
|
)
|
|
)
|
|
)
|
|
fsm.act("HEADER-RECEIVE",
|
|
sink.ready.eq(1),
|
|
If(sink.valid,
|
|
NextValue(count, count + 1),
|
|
sr_shift.eq(1),
|
|
If(count == (header_words - 1),
|
|
If(header_leftover,
|
|
NextValue(count, count + 1),
|
|
NextState("UNALIGNED-DATA-COPY")
|
|
).Else(
|
|
NextState("ALIGNED-DATA-COPY")
|
|
)
|
|
)
|
|
)
|
|
)
|
|
self.sync += If(sink.valid & sink.ready, sink_d.eq(sink))
|
|
fsm.act("UNALIGNED-DATA-COPY",
|
|
source.valid.eq(sink.valid | sink_d.last),
|
|
source.last.eq(sink.last | sink_d.last),
|
|
sink.ready.eq(source.ready),
|
|
source.data.eq(sink_d.data[header_leftover*8:]),
|
|
source.data[min((bytes_per_clk-header_leftover)*8, data_width-1):].eq(sink.data),
|
|
If(fsm_from_idle,
|
|
source.valid.eq(sink_d.last),
|
|
sink.ready.eq(1),
|
|
If(sink.valid,
|
|
NextValue(fsm_from_idle, 0),
|
|
sr_shift_leftover.eq(1),
|
|
)
|
|
),
|
|
If(source.valid & source.ready,
|
|
If(source.last,
|
|
NextState("IDLE")
|
|
)
|
|
)
|
|
)
|
|
fsm.act("ALIGNED-DATA-COPY",
|
|
source.valid.eq(sink.valid | sink_d.last),
|
|
source.last.eq(sink.last | sink_d.last),
|
|
sink.ready.eq(source.ready),
|
|
source.data.eq(sink.data),
|
|
If(source.valid & source.ready,
|
|
If(source.last,
|
|
NextState("IDLE")
|
|
)
|
|
)
|
|
)
|
|
|
|
# Error ------------------------------------------------------------------------------------
|
|
if hasattr(sink, "error") and hasattr(source, "error"):
|
|
self.comb += source.error.eq(sink.error)
|
|
|
|
# Last BE ----------------------------------------------------------------------------------
|
|
if hasattr(sink, "last_be") and hasattr(source, "last_be"):
|
|
x = [sink.last_be[(i - (bytes_per_clk - header_leftover))%bytes_per_clk]
|
|
for i in range(bytes_per_clk)]
|
|
self.comb += source.last_be.eq(Cat(*x))
|