periph: conform with nmigen-soc breaking changes.
[lambdasoc.git] / lambdasoc / soc / base.py
1 import os
2 import re
3 import textwrap
4 import jinja2
5
6 from collections import OrderedDict
7 from collections.abc import Mapping
8
9 from nmigen import tracer
10 from nmigen.utils import log2_int
11 from nmigen.build.run import *
12
13 from nmigen_soc.memory import MemoryMap
14 from nmigen_soc.periph import ConstantMap, ConstantBool, ConstantInt
15
16 from .. import __version__, software
17 from ..periph import Peripheral
18
19
20 __all__ = ["socproperty", "ConstantAddr", "ConstantMapCollection", "SoC", "ConfigBuilder"]
21
22
23 def socproperty(cls, *, weak=False, src_loc_at=0):
24 name = tracer.get_var_name(depth=2 + src_loc_at)
25 __name = "__{}".format(name)
26
27 def getter(self):
28 assert isinstance(self, SoC)
29 attr = getattr(self, __name, None)
30 if attr is None and not weak:
31 raise NotImplementedError("SoC {!r} does not have a {}"
32 .format(self, name))
33 return attr
34
35 def setter(self, value):
36 assert isinstance(self, SoC)
37 if not isinstance(value, cls):
38 raise TypeError("{} must be an instance of {}, not {!r}"
39 .format(name, cls.__name__, value))
40 setattr(self, __name, value)
41
42 return property(getter, setter)
43
44
45 class ConstantAddr(ConstantInt):
46 def __init__(self, value, *, width=None):
47 return super().__init__(value, width=width, signed=False)
48
49 def __repr__(self):
50 return "ConstantAddr({}, width={})".format(self.value, self.width)
51
52
53 class ConstantMapCollection(Mapping):
54 def __init__(self, **constant_maps):
55 self._storage = OrderedDict()
56 for key, value in constant_maps.items():
57 if value is None:
58 pass
59 elif not isinstance(value, (ConstantMap, ConstantMapCollection)):
60 raise TypeError("Constant map must be an instance of ConstantMap or "
61 "ConstantMapCollection, not {!r}"
62 .format(value))
63 self._storage[key] = value
64
65 def flatten(self, *, prefix="", separator="_"):
66 if not isinstance(prefix, str):
67 raise TypeError("Prefix must be a string, not {!r}".format(prefix))
68 if not isinstance(separator, str):
69 raise TypeError("Separator must be a string, not {!r}".format(separator))
70 for key, value in self.items():
71 if isinstance(value, ConstantMap):
72 for const_key, const_value in value.items():
73 yield f"{prefix}{key}{separator}{const_key}", const_value
74 elif isinstance(value, ConstantMapCollection):
75 yield from value.flatten(prefix=f"{prefix}{key}{separator}", separator=separator)
76
77 def union(self, **other):
78 union = OrderedDict()
79 for key in self.keys() | other.keys():
80 self_value = self .get(key, None)
81 other_value = other.get(key, None)
82 if self_value is None or other_value is None:
83 union[key] = self_value or other_value
84 elif isinstance(self_value, ConstantMap):
85 if not isinstance(other_value, ConstantMap):
86 raise TypeError # TODO
87 union[key] = ConstantMap(**self_value, **other_value)
88 elif isinstance(self_value, ConstantMapCollection):
89 if not isinstance(other_value, ConstantMapCollection):
90 raise TypeError # TODO
91 union[key] = self_value.merge(**{key: other_value})
92 else:
93 assert False
94 return ConstantMapCollection(**union)
95
96 def __getitem__(self, prefix):
97 return self._storage[prefix]
98
99 def __iter__(self):
100 yield from self._storage
101
102 def __len__(self):
103 return len(self._storage)
104
105 def __repr__(self):
106 return "ConstantMapCollection({})".format(list(self._storage.items()))
107
108
109 class SoC:
110 memory_map = socproperty(MemoryMap)
111 constants = socproperty(ConstantMapCollection, weak=True)
112
113 def build(self, build_dir="build/soc", do_build=True, name=None):
114 plan = ConfigBuilder().prepare(self, build_dir, name)
115 if not do_build:
116 return plan
117
118 products = plan.execute_local(build_dir)
119 return products
120
121
122 class ConfigBuilder:
123 file_templates = {
124 "build_{{name}}.sh": r"""
125 # {{autogenerated}}
126 set -e
127 {{emit_commands()}}
128 """,
129 "{{name}}_resources.csv": r"""
130 # {{autogenerated}}
131 # <resource name>, <start address>, <end address>, <access width>
132 {% for res_info in soc.memory_map.all_resources() -%}
133 {{"_".join(res_info.name)}}, {{hex(res_info.start)}}, {{hex(res_info.end)}}, {{res_info.width}}
134 {% endfor %}
135 """,
136 }
137 command_templates = []
138
139 def prepare(self, soc, build_dir, name, **render_params):
140 name = name or type(soc).__name__.lower()
141
142 autogenerated = "Automatically generated by LambdaSoC {}. Do not edit.".format(__version__)
143
144 def periph_addr(periph):
145 assert isinstance(periph, Peripheral)
146 periph_map = periph.bus.memory_map
147 for window, (start, end, ratio) in soc.memory_map.windows():
148 if periph_map is window:
149 return start
150 raise KeyError(periph)
151
152 def periph_size(periph):
153 assert isinstance(periph, Peripheral)
154 granularity_bits = log2_int(periph.bus.data_width // periph.bus.granularity)
155 return 2**(periph.bus.addr_width + granularity_bits)
156
157 def emit_commands():
158 commands = []
159 for index, command_tpl in enumerate(self.command_templates):
160 command = render(command_tpl, origin="<command#{}>".format(index + 1))
161 command = re.sub(r"\s+", " ", command)
162 commands.append(command)
163 return "\n".join(commands)
164
165 def render(source, origin):
166 try:
167 source = textwrap.dedent(source).strip()
168 compiled = jinja2.Template(source, trim_blocks=True, lstrip_blocks=True)
169 except jinja2.TemplateSyntaxError as e:
170 e.args = ("{} (at {}:{})".format(e.message, origin, e.lineno),)
171 raise
172 return compiled.render({
173 "autogenerated": autogenerated,
174 "build_dir": os.path.abspath(build_dir),
175 "emit_commands": emit_commands,
176 "hex": hex,
177 "name": name,
178 "periph_addr": periph_addr,
179 "periph_size": periph_size,
180 "soc": soc,
181 "software_dir": os.path.dirname(software.__file__),
182 **render_params,
183 })
184
185 plan = BuildPlan(script="build_{}".format(name))
186 for filename_tpl, content_tpl in self.file_templates.items():
187 plan.add_file(render(filename_tpl, origin=filename_tpl),
188 render(content_tpl, origin=content_tpl))
189 return plan