921cb554b57bb5ff2aed10117e8b0539fbdaf964
[gram.git] / gram / stream.py
1 from nmigen import *
2 from nmigen.hdl.rec import *
3 from nmigen.lib import fifo
4
5
6 __all__ = ["Endpoint", "SyncFIFO", "AsyncFIFO", "Buffer"]
7
8
9 def _make_fanout(layout):
10 r = []
11 for f in layout:
12 if isinstance(f[1], (int, tuple)):
13 r.append((f[0], f[1], DIR_FANOUT))
14 else:
15 r.append((f[0], _make_fanout(f[1])))
16 return r
17
18
19 class EndpointDescription:
20 def __init__(self, payload_layout):
21 self.payload_layout = payload_layout
22
23 def get_full_layout(self):
24 reserved = {"valid", "ready", "first", "last", "payload"}
25 attributed = set()
26 for f in self.payload_layout:
27 if f[0] in attributed:
28 raise ValueError(f[0] + " already attributed in payload layout")
29 if f[0] in reserved:
30 raise ValueError(f[0] + " cannot be used in endpoint layout")
31 attributed.add(f[0])
32
33 full_layout = [
34 ("valid", 1, DIR_FANOUT),
35 ("ready", 1, DIR_FANIN),
36 ("first", 1, DIR_FANOUT),
37 ("last", 1, DIR_FANOUT),
38 ("payload", _make_fanout(self.payload_layout))
39 ]
40 return full_layout
41
42
43 class Endpoint(Record):
44 def __init__(self, layout_or_description, **kwargs):
45 if isinstance(layout_or_description, EndpointDescription):
46 self.description = layout_or_description
47 else:
48 self.description = EndpointDescription(layout_or_description)
49 super().__init__(self.description.get_full_layout(), src_loc_at=1, **kwargs)
50
51 def __getattr__(self, name):
52 try:
53 return super().__getattr__(name)
54 except AttributeError:
55 return self.fields["payload"][name]
56
57
58 class _FIFOWrapper:
59 def __init__(self, payload_layout):
60 self.sink = Endpoint(payload_layout)
61 self.source = Endpoint(payload_layout)
62
63 self.layout = Layout([
64 ("payload", self.sink.description.payload_layout),
65 ("first", 1, DIR_FANOUT),
66 ("last", 1, DIR_FANOUT)
67 ])
68
69 def elaborate(self, platform):
70 m = Module()
71
72 fifo = m.submodules.fifo = self.fifo
73 fifo_din = Record(self.layout)
74 fifo_dout = Record(self.layout)
75 m.d.comb += [
76 fifo.w_data.eq(fifo_din),
77 fifo_dout.eq(fifo.r_data),
78
79 self.sink.ready.eq(fifo.w_rdy),
80 fifo.w_en.eq(self.sink.valid),
81 fifo_din.first.eq(self.sink.first),
82 fifo_din.last.eq(self.sink.last),
83 fifo_din.payload.eq(self.sink.payload),
84
85 self.source.valid.eq(fifo.r_rdy),
86 self.source.first.eq(fifo_dout.first),
87 self.source.last.eq(fifo_dout.last),
88 self.source.payload.eq(fifo_dout.payload),
89 fifo.r_en.eq(self.source.ready)
90 ]
91
92 return m
93
94
95 class SyncFIFO(Elaboratable, _FIFOWrapper):
96 def __init__(self, layout, depth, fwft=True, buffered=False):
97 super().__init__(layout)
98 if buffered:
99 self.fifo = fifo.SyncFIFOBuffered(width=len(Record(self.layout)), depth=depth, fwft=fwft)
100 else:
101 self.fifo = fifo.SyncFIFO(width=len(Record(self.layout)), depth=depth, fwft=fwft)
102 self.depth = self.fifo.depth
103 self.level = self.fifo.level
104
105
106 class AsyncFIFO(Elaboratable, _FIFOWrapper):
107 def __init__(self, layout, depth, r_domain="read", w_domain="write"):
108 super().__init__(layout)
109 self.fifo = fifo.AsyncFIFO(width=len(Record(self.layout)), depth=depth,
110 r_domain=r_domain, w_domain=w_domain)
111 self.depth = self.fifo.depth
112
113 class PipeValid(Elaboratable):
114 """Pipe valid/payload to cut timing path"""
115 def __init__(self, layout):
116 self.sink = Endpoint(layout)
117 self.source = Endpoint(layout)
118
119 def elaborate(self, platform):
120 m = Module()
121
122 # Pipe when source is not valid or is ready.
123 with m.If(~self.source.valid | self.source.ready):
124 m.d.sync += [
125 self.source.valid.eq(self.sink.valid),
126 self.source.first.eq(self.sink.first),
127 self.source.last.eq(self.sink.last),
128 self.source.payload.eq(self.sink.payload),
129 self.source.param.eq(self.sink.param),
130 ]
131 m.d.comb += self.sink.ready.eq(~self.source.valid | self.source.ready)
132
133 return m
134
135 class Buffer(PipeValid): pass # FIXME: Replace Buffer with PipeValid in codebase?