add grant links, and record of funding under #538
[nmutil.git] / src / nmutil / nmoperator.py
1 """ nmigen operator functions / utils
2
3 This work is funded through NLnet under Grant 2019-02-012
4
5 License: LGPLv3+
6
7
8 eq:
9 --
10
11 a strategically very important function that is identical in function
12 to nmigen's Signal.eq function, except it may take objects, or a list
13 of objects, or a tuple of objects, and where objects may also be
14 Records.
15 """
16
17 from nmigen import Signal, Cat, Value
18 from nmigen.hdl.ast import ArrayProxy
19 from nmigen.hdl.rec import Record, Layout
20
21 from abc import ABCMeta, abstractmethod
22 from collections.abc import Sequence, Iterable
23 import inspect
24
25
26 class Visitor2:
27 """ a helper class for iterating twin-argument compound data structures.
28
29 Record is a special (unusual, recursive) case, where the input may be
30 specified as a dictionary (which may contain further dictionaries,
31 recursively), where the field names of the dictionary must match
32 the Record's field spec. Alternatively, an object with the same
33 member names as the Record may be assigned: it does not have to
34 *be* a Record.
35
36 ArrayProxy is also special-cased, it's a bit messy: whilst ArrayProxy
37 has an eq function, the object being assigned to it (e.g. a python
38 object) might not. despite the *input* having an eq function,
39 that doesn't help us, because it's the *ArrayProxy* that's being
40 assigned to. so.... we cheat. use the ports() function of the
41 python object, enumerate them, find out the list of Signals that way,
42 and assign them.
43 """
44 def iterator2(self, o, i):
45 if isinstance(o, dict):
46 yield from self.dict_iter2(o, i)
47
48 if not isinstance(o, Sequence):
49 o, i = [o], [i]
50 for (ao, ai) in zip(o, i):
51 #print ("visit", fn, ao, ai)
52 if isinstance(ao, Record):
53 yield from self.record_iter2(ao, ai)
54 elif isinstance(ao, ArrayProxy) and not isinstance(ai, Value):
55 yield from self.arrayproxy_iter2(ao, ai)
56 else:
57 yield (ao, ai)
58
59 def dict_iter2(self, o, i):
60 for (k, v) in o.items():
61 print ("d-iter", v, i[k])
62 yield (v, i[k])
63 return res
64
65 def _not_quite_working_with_all_unit_tests_record_iter2(self, ao, ai):
66 print ("record_iter2", ao, ai, type(ao), type(ai))
67 if isinstance(ai, Value):
68 if isinstance(ao, Sequence):
69 ao, ai = [ao], [ai]
70 for o, i in zip(ao, ai):
71 yield (o, i)
72 return
73 for idx, (field_name, field_shape, _) in enumerate(ao.layout):
74 if isinstance(field_shape, Layout):
75 val = ai.fields
76 else:
77 val = ai
78 if hasattr(val, field_name): # check for attribute
79 val = getattr(val, field_name)
80 else:
81 val = val[field_name] # dictionary-style specification
82 yield from self.iterator2(ao.fields[field_name], val)
83
84 def record_iter2(self, ao, ai):
85 for idx, (field_name, field_shape, _) in enumerate(ao.layout):
86 if isinstance(field_shape, Layout):
87 val = ai.fields
88 else:
89 val = ai
90 if hasattr(val, field_name): # check for attribute
91 val = getattr(val, field_name)
92 else:
93 val = val[field_name] # dictionary-style specification
94 yield from self.iterator2(ao.fields[field_name], val)
95
96 def arrayproxy_iter2(self, ao, ai):
97 #print ("arrayproxy_iter2", ai.ports(), ai, ao)
98 for p in ai.ports():
99 #print ("arrayproxy - p", p, p.name, ao)
100 op = getattr(ao, p.name)
101 yield from self.iterator2(op, p)
102
103
104 class Visitor:
105 """ a helper class for iterating single-argument compound data structures.
106 similar to Visitor2.
107 """
108 def iterate(self, i):
109 """ iterate a compound structure recursively using yield
110 """
111 if not isinstance(i, Sequence):
112 i = [i]
113 for ai in i:
114 #print ("iterate", ai)
115 if isinstance(ai, Record):
116 #print ("record", list(ai.layout))
117 yield from self.record_iter(ai)
118 elif isinstance(ai, ArrayProxy) and not isinstance(ai, Value):
119 yield from self.array_iter(ai)
120 else:
121 yield ai
122
123 def record_iter(self, ai):
124 for idx, (field_name, field_shape, _) in enumerate(ai.layout):
125 if isinstance(field_shape, Layout):
126 val = ai.fields
127 else:
128 val = ai
129 if hasattr(val, field_name): # check for attribute
130 val = getattr(val, field_name)
131 else:
132 val = val[field_name] # dictionary-style specification
133 #print ("recidx", idx, field_name, field_shape, val)
134 yield from self.iterate(val)
135
136 def array_iter(self, ai):
137 for p in ai.ports():
138 yield from self.iterate(p)
139
140
141 def eq(o, i):
142 """ makes signals equal: a helper routine which identifies if it is being
143 passed a list (or tuple) of objects, or signals, or Records, and calls
144 the objects' eq function.
145 """
146 res = []
147 for (ao, ai) in Visitor2().iterator2(o, i):
148 rres = ao.eq(ai)
149 if not isinstance(rres, Sequence):
150 rres = [rres]
151 res += rres
152 return res
153
154
155 def shape(i):
156 #print ("shape", i)
157 r = 0
158 for part in list(i):
159 #print ("shape?", part)
160 s, _ = part.shape()
161 r += s
162 return r, False
163
164
165 def cat(i):
166 """ flattens a compound structure recursively using Cat
167 """
168 from nmigen._utils import flatten
169 #res = list(flatten(i)) # works (as of nmigen commit f22106e5) HOWEVER...
170 res = list(Visitor().iterate(i)) # needed because input may be a sequence
171 return Cat(*res)
172
173