ceb3663c021c6a5e2365f6470f0983282cbecbc8
[lambdasoc.git] / lambdasoc / soc / base.py
1 import os
2 import re
3 import textwrap
4 import jinja2
5
6 from nmigen import tracer
7 from nmigen_soc.memory import MemoryMap
8 from nmigen.build.run import *
9
10 from .. import __version__, software
11 from ..periph import Peripheral
12
13
14 __all__ = ["socproperty", "SoC", "ConfigBuilder"]
15
16
17 def socproperty(cls, *, weak=False, src_loc_at=0):
18 name = tracer.get_var_name(depth=2 + src_loc_at)
19 __name = "__{}".format(name)
20
21 def getter(self):
22 assert isinstance(self, SoC)
23 attr = getattr(self, __name, None)
24 if attr is None and not weak:
25 raise NotImplementedError("SoC {!r} does not have a {}"
26 .format(self, name))
27 return attr
28
29 def setter(self, value):
30 assert isinstance(self, SoC)
31 if not isinstance(value, cls):
32 raise TypeError("{} must be an instance of {}, not {!r}"
33 .format(name, cls.__name__, value))
34 setattr(self, __name, value)
35
36 return property(getter, setter)
37
38
39 class SoC:
40 memory_map = socproperty(MemoryMap)
41
42 def build(self, build_dir="build/soc", do_build=True, name=None):
43 plan = ConfigBuilder().prepare(self, build_dir, name)
44 if not do_build:
45 return plan
46
47 products = plan.execute_local(build_dir)
48 return products
49
50
51 class ConfigBuilder:
52 file_templates = {
53 "build_{{name}}.sh": r"""
54 # {{autogenerated}}
55 set -e
56 {{emit_commands()}}
57 """,
58 "{{name}}_resources.csv": r"""
59 # {{autogenerated}}
60 # <resource name>, <start address>, <end address>, <access width>
61 {% for resource, (start, end, width) in soc.memory_map.all_resources() -%}
62 {{resource.name}}, {{hex(start)}}, {{hex(end)}}, {{width}}
63 {% endfor %}
64 """,
65 }
66 command_templates = []
67
68 def prepare(self, soc, build_dir, name):
69 name = name or type(soc).__name__.lower()
70
71 autogenerated = "Automatically generated by LambdaSoC {}. Do not edit.".format(__version__)
72
73 def periph_addr(periph):
74 assert isinstance(periph, Peripheral)
75 periph_map = periph.bus.memory_map
76 for window, (start, end, ratio) in soc.memory_map.windows():
77 if periph_map is window:
78 return start
79 raise KeyError(periph)
80
81 def periph_csrs(periph):
82 assert isinstance(periph, Peripheral)
83 periph_map = periph.bus.memory_map
84 for resource, (start, end, width) in periph_map.all_resources():
85 if isinstance(resource, csr.Element):
86 yield resource, (start, end, width)
87
88 def emit_commands():
89 commands = []
90 for index, command_tpl in enumerate(self.command_templates):
91 command = render(command_tpl, origin="<command#{}>".format(index + 1))
92 command = re.sub(r"\s+", " ", command)
93 commands.append(command)
94 return "\n".join(commands)
95
96 def render(source, origin):
97 try:
98 source = textwrap.dedent(source).strip()
99 compiled = jinja2.Template(source, trim_blocks=True, lstrip_blocks=True)
100 except jinja2.TemplateSyntaxError as e:
101 e.args = ("{} (at {}:{})".format(e.message, origin, e.lineno),)
102 raise
103 return compiled.render({
104 "autogenerated": autogenerated,
105 "build_dir": os.path.abspath(build_dir),
106 "emit_commands": emit_commands,
107 "hex": hex,
108 "name": name,
109 "periph_addr": periph_addr,
110 "periph_csrs": periph_csrs,
111 "soc": soc,
112 "software_dir": os.path.dirname(software.__file__),
113 })
114
115 plan = BuildPlan(script="build_{}".format(name))
116 for filename_tpl, content_tpl in self.file_templates.items():
117 plan.add_file(render(filename_tpl, origin=filename_tpl),
118 render(content_tpl, origin=content_tpl))
119 return plan