speed up ==, hash, <, >, <=, and >= for plain_data
[nmutil.git] / src / nmutil / util.py
1 # SPDX-License-Identifier: LGPL-3-or-later
2 """
3 This work is funded through NLnet under Grant 2019-02-012
4
5 License: LGPLv3+
6
7 """
8
9 from collections.abc import Iterable
10 from nmigen import Mux, Signal, Cat
11
12
13 # XXX this already exists in nmigen._utils
14 # see https://bugs.libre-soc.org/show_bug.cgi?id=297
15 def flatten(v):
16 if isinstance(v, Iterable):
17 for i in v:
18 yield from flatten(i)
19 else:
20 yield v
21
22
23 # tree reduction function. operates recursively.
24 def treereduce(tree, op, fn=None):
25 """treereduce: apply a map-reduce to a list, reducing to a single item
26
27 this is *not* the same as "x = Signal(64) reduce(x, operator.add)",
28 which is a bit-wise reduction down to a single bit
29
30 it is "l = [Signal(w), ..., Signal(w)] reduce(l, operator.add)"
31 i.e. l[0] + l[1] ...
32
33 examples: OR-reduction of one member of a list of Records down to a
34 single value:
35 treereduce(tree, operator.or_, lambda x: getattr(x, "o_data"))
36 """
37 if fn is None:
38 def fn(x): return x
39 if not isinstance(tree, list):
40 return tree
41 if len(tree) == 1:
42 return fn(tree[0])
43 if len(tree) == 2:
44 return op(fn(tree[0]), fn(tree[1]))
45 s = len(tree) // 2 # splitpoint
46 return op(treereduce(tree[:s], op, fn),
47 treereduce(tree[s:], op, fn))
48
49 # chooses assignment of 32 bit or full 64 bit depending on is_32bit
50
51
52 def eq32(is_32bit, dest, src):
53 return [dest[0:32].eq(src[0:32]),
54 dest[32:64].eq(Mux(is_32bit, 0, src[32:64]))]
55
56
57 # a wrapper function formerly in run_simulation that is still useful.
58 # Simulation.add_sync_process now only takes functions, it does not
59 # take generators. so passing in arguments is no longer possible.
60 # with this wrapper, the following is possible:
61 # sim.add_sync_process(wrap.dut(parallel_sender_number=0))
62 # sim.add_sync_process(wrap.dut(parallel_sender_number=1))
63
64 def wrap(process):
65 def wrapper():
66 yield from process
67 return wrapper
68
69
70 # a "rising edge" generator. can take signals of greater than width 1
71
72 def rising_edge(m, sig):
73 delay = Signal.like(sig)
74 rising = Signal.like(sig)
75 delay.name = "%s_dly" % sig.name
76 rising.name = "%s_rise" % sig.name
77 m.d.sync += delay.eq(sig) # 1 clock delay
78 m.d.comb += rising.eq(sig & ~delay) # sig is hi but delay-sig is lo
79 return rising
80
81
82 # Display function (dummy if non-existent)
83 # added as a patch from jeanthom
84 # https://gist.githubusercontent.com/jeanthom/
85 # f97f5b928720d4adda9d295e8a5bc078/
86 # raw/694274e0aceec993c0fc127e296b1a85b93c1b89/nmigen-display.diff
87 try:
88 from nmigen.hdl.ast import Display
89 except ImportError:
90 def Display(*args):
91 return []
92
93
94 def sel(m, r, sel_bits, field_width=None, name=None, src_loc_at=0):
95 """Forms a subfield from a selection of bits of the signal `r`
96 ("register").
97
98 :param m: nMigen Module for adding the wires
99 :param r: signal containing the field from which to select the subfield
100 :param sel_bits: bit indices of the subfield, in "MSB 0" convention,
101 from most significant to least significant. Note that
102 the indices are allowed to be non-contiguous and/or
103 out-of-order.
104 :param field_width: field width. If absent, use the signal `r` own width.
105 :param name: name of the generated Signal
106 :param src_loc_at: in the absence of `name`, stack level in which
107 to find it
108
109 :returns: a new Signal which gets assigned to the subfield
110 """
111 # find the MSB index in LSB0 numbering
112 if field_width is None:
113 msb = len(r) - 1
114 else:
115 msb = field_width - 1
116 # extract the selected bits
117 sig_list = []
118 for idx in sel_bits:
119 sig_list.append(r[msb - idx])
120 # place the LSB at the front of the list,
121 # since, in nMigen, Cat starts from the LSB
122 sig_list.reverse()
123 sel_ret = Signal(len(sig_list), name=name, src_loc_at=src_loc_at+1)
124 m.d.comb += sel_ret.eq(Cat(*sig_list))
125 return sel_ret