Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Streaming FIFO: Signals Read/Write Availability using Watermarks #150

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 121 additions & 6 deletions misoc/interconnect/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self, payload_layout):
self.payload_layout = payload_layout

def get_full_layout(self):
reserved = {"stb", "ack", "payload", "eop", "description"}
reserved = {"stb", "ack", "payload", "last", "eop", "description"}
attributed = set()
for f in self.payload_layout:
if f[0] in attributed:
Expand All @@ -30,6 +30,7 @@ def get_full_layout(self):
full_layout = [
("stb", 1, DIR_M_TO_S),
("ack", 1, DIR_S_TO_M),
("last", 1, DIR_M_TO_S),
("eop", 1, DIR_M_TO_S),
("payload", _make_m2s(self.payload_layout))
]
Expand All @@ -49,7 +50,7 @@ def __getattr__(self, name):


class _FIFOWrapper(Module):
def __init__(self, fifo_class, layout, depth):
def __init__(self, fifo_class, layout, depth, hi_wm=None, lo_wm=None):
self.sink = Endpoint(layout)
self.source = Endpoint(layout)

Expand All @@ -58,7 +59,13 @@ def __init__(self, fifo_class, layout, depth):
description = self.sink.description
fifo_layout = [("payload", description.payload_layout), ("eop", 1)]

self.submodules.fifo = fifo_class(layout_len(fifo_layout), depth)
watermark_args = {}
if hi_wm is not None:
watermark_args["hi_wm"] = hi_wm
if lo_wm is not None:
watermark_args["lo_wm"] = lo_wm

self.submodules.fifo = fifo_class(layout_len(fifo_layout), depth, **watermark_args)
fifo_in = Record(fifo_layout)
fifo_out = Record(fifo_layout)
self.comb += [
Expand All @@ -78,13 +85,116 @@ def __init__(self, fifo_class, layout, depth):
self.fifo.re.eq(self.source.ack)
]

# Burst transfer with the use of watermarks:
# FIFO would expect complete bursts to be written to/read from, when
# given the corresponding watermark arguments, unless the burst is
# prematurely terminated by asserting sink.eop.
# A complete burst is a continuous lo_wm/hi_wm of words written to/
# read from the FIFO.

# With high watermark, FIFO mandates hi_wm length burst read.
#
# source.stb is held low if no complete burst/buffered packet could
# be transferred.
# source.last is driven high to signal end of burst.
if hi_wm is not None:
transfer_count = Signal(max=hi_wm, reset=hi_wm-1)
transfer_count_ce = Signal()
transfer_count_rst = Signal()
activated = Signal()
eop_count = Signal(max=depth+1)
eop_count_next = Signal(max=depth+1)
has_pending_eop = Signal()

# helper signals
do_write = Signal()
do_read = Signal()

self.sync += [
If(transfer_count_rst,
transfer_count.eq(transfer_count.reset),
).Elif(transfer_count_ce,
transfer_count.eq(transfer_count - 1),
),
eop_count.eq(eop_count_next),
]

self.comb += [
# Avoid downstream overreading
self.fifo.re.eq(self.source.stb & self.source.ack),

do_write.eq(self.fifo.we & self.fifo.writable),
do_read.eq(self.fifo.re & self.fifo.readable),
has_pending_eop.eq(eop_count_next != 0),

eop_count_next.eq(eop_count),

If(fifo_in.eop & do_write,
If(~(fifo_out.eop & do_read),
eop_count_next.eq(eop_count + 1),
),
).Elif(fifo_out.eop & do_read,
eop_count_next.eq(eop_count - 1),
),
]

# Stream control
self.comb += [
self.source.last.eq((transfer_count == 0) | fifo_out.eop),
self.source.stb.eq(self.fifo.readable & (self.fifo.almost_full | activated)),
transfer_count_ce.eq(do_read),
transfer_count_rst.eq(do_read & self.source.last),
]

self.sync += [
If(~activated,
activated.eq(self.fifo.almost_full | (self.sink.eop & do_write))
).Elif(do_read & self.source.last,
activated.eq(has_pending_eop),
),
]

# With low watermark, FIFO accepts lo_wm length burst write.
#
# sink.last must indicate the end of burst
#
# It is the upstream's duty to drive sink.last signal appropriately.
if lo_wm is not None:
recv_activated = Signal()

# helper signals
do_write = Signal()

self.comb += [
do_write.eq(self.fifo.we & self.fifo.writable),

# Avoid upstream overwriting
self.fifo.we.eq(self.sink.stb & self.sink.ack),
]

# recv stream control
self.comb += \
self.sink.ack.eq(self.fifo.writable & (
self.fifo.almost_empty # Can accept long burst
| recv_activated # In the middle of a burst
))

self.sync += \
If(~recv_activated,
# Avoid entry to burst state if it is a 1 word burst
recv_activated.eq(self.fifo.almost_empty & ~(do_write & self.sink.eop)),
).Elif(recv_activated & (do_write & (self.sink.last | self.sink.eop)),
# almost_empty needs 1 cycle to update
recv_activated.eq(0),
)


class SyncFIFO(_FIFOWrapper):
def __init__(self, layout, depth, buffered=False):
def __init__(self, layout, depth, buffered=False, hi_wm=None, lo_wm=None):
_FIFOWrapper.__init__(
self,
fifo.SyncFIFOBuffered if buffered else fifo.SyncFIFO,
layout, depth)
layout, depth, hi_wm, lo_wm)


class AsyncFIFO(_FIFOWrapper):
Expand Down Expand Up @@ -147,7 +257,9 @@ def __init__(self, nbits_from, nbits_to, ratio, reverse,
self.comb += [
sink.ack.eq(~strobe_all | source.ack),
source.stb.eq(strobe_all),
load_part.eq(sink.stb & sink.ack)
load_part.eq(sink.stb & sink.ack),
# cannot burst
source.last.eq(1)
]

demux_last = ((demux == (ratio - 1)) | sink.eop)
Expand Down Expand Up @@ -199,6 +311,7 @@ def __init__(self, nbits_from, nbits_to, ratio, reverse,
last.eq(mux == (ratio-1)),
source.stb.eq(sink.stb),
source.eop.eq(sink.eop & last),
source.last.eq(sink.last & last),
sink.ack.eq(last & source.ack)
]
self.sync += \
Expand Down Expand Up @@ -283,6 +396,7 @@ def __init__(self, layout_from, layout_to, *args, **kwargs):
# cast sink to converter.sink (user fields --> raw bits)
self.comb += [
converter.sink.stb.eq(sink.stb),
converter.sink.last.eq(sink.last),
converter.sink.eop.eq(sink.eop),
sink.ack.eq(converter.sink.ack)
]
Expand All @@ -302,6 +416,7 @@ def __init__(self, layout_from, layout_to, *args, **kwargs):
# cast converter.source to source (raw bits --> user fields)
self.comb += [
source.stb.eq(converter.source.stb),
source.last.eq(converter.source.last),
source.eop.eq(converter.source.eop),
converter.source.ack.eq(source.ack)
]
Expand Down