@h2loop/autosar 0.1.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/agents/autosar-integrator.md +34 -2
- package/package.json +4 -1
- package/plugins/index.js +53 -23
- package/python/ecuc_engine/__init__.py +1 -1
- package/python/ecuc_engine/add_signal.py +351 -0
- package/python/ecuc_engine/arxml_edit.py +387 -0
- package/python/ecuc_engine/audit.py +372 -69
- package/python/ecuc_engine/cli.py +119 -10
- package/python/ecuc_engine/codegen/__init__.py +185 -0
- package/python/ecuc_engine/codegen/binding.py +210 -0
- package/python/ecuc_engine/codegen/bindings/sip_standin.json +111 -0
- package/python/ecuc_engine/codegen/cdiff.py +395 -0
- package/python/ecuc_engine/codegen/cwriter.py +150 -0
- package/python/ecuc_engine/codegen/emit.py +395 -0
- package/python/ecuc_engine/codegen/handles.py +89 -0
- package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
- package/python/ecuc_engine/codegen/model.py +259 -0
- package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
- package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
- package/python/ecuc_engine/codegen/modules/dem.py +22 -0
- package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
- package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
- package/python/ecuc_engine/codegen/registry.py +133 -0
- package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
- package/python/ecuc_engine/context.py +481 -0
- package/python/ecuc_engine/csrc.py +189 -0
- package/python/ecuc_engine/defs.py +30 -0
- package/python/ecuc_engine/diff.py +13 -5
- package/python/ecuc_engine/find.py +162 -0
- package/python/ecuc_engine/gen.py +105 -0
- package/python/ecuc_engine/history.py +146 -0
- package/python/ecuc_engine/model.py +33 -29
- package/python/ecuc_engine/paths.py +221 -0
- package/python/ecuc_engine/probe.py +229 -0
- package/python/ecuc_engine/rte_api.py +935 -0
- package/python/ecuc_engine/rules/__init__.py +5 -3
- package/python/ecuc_engine/rules/mem.py +84 -0
- package/python/ecuc_engine/rules/nvm.py +131 -0
- package/python/ecuc_engine/rules/pdur.py +87 -0
- package/python/ecuc_engine/rules/system.py +162 -0
- package/python/ecuc_engine/sysmodel.py +152 -0
- package/python/ecuc_engine/validate.py +52 -3
- package/python/ecuc_engine/verify_rte.py +445 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +57 -1
- package/skills/autosar/references/codegen.md +30 -0
- package/skills/autosar/references/project-layout.md +11 -2
- package/skills/autosar/references/runnable-code.md +50 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Generator view of the ECUC model: merged containers, effective config classes, refs, symbols.
|
|
2
|
+
|
|
3
|
+
Everything here is derived from the engine's loader (`model.Project`) and the param defs
|
|
4
|
+
(`defs.DefIndex`); nothing is module-specific. Container order is document order (files sorted by
|
|
5
|
+
path, elements in file order), so every table built from it is deterministic.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Iterable, Optional
|
|
12
|
+
|
|
13
|
+
from .. import defs as D
|
|
14
|
+
from ..model import Elem, ParamVal, Project
|
|
15
|
+
from ..values import parse_float, parse_int
|
|
16
|
+
|
|
17
|
+
CLASS_RANK = {"PUBLISHED-INFORMATION": 0, "PRE-COMPILE": 1, "LINK": 2, "POST-BUILD": 3}
|
|
18
|
+
CLASS_FILE = {0: "Cfg.c", 1: "Cfg.c", 2: "Lcfg.c", 3: "PBcfg.c"}
|
|
19
|
+
DEFAULT_VARIANT = "VARIANT-PRE-COMPILE"
|
|
20
|
+
|
|
21
|
+
# AUTOSAR_CP_TR_ReleaseOverview R25-11, Table 1.2 "Mapping schema version to platform releases"
|
|
22
|
+
# (and Table 1.1 for the R4.x schemas): schema AUTOSAR_000NN -> internal release number.
|
|
23
|
+
SCHEMA_RELEASE = {
|
|
24
|
+
"00042": (4, 3, 0), "00043": (4, 3, 0), "00044": (4, 3, 1), "00045": (4, 3, 1), "00046": (4, 4, 0),
|
|
25
|
+
"00047": (4, 4, 0), "00048": (4, 5, 0), "00049": (4, 6, 0), "00050": (4, 7, 0), "00051": (4, 8, 0),
|
|
26
|
+
"00052": (4, 9, 0), "00053": (4, 10, 0), "00054": (4, 11, 0),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def snake(name: str) -> str:
|
|
31
|
+
"""CamelCase -> UPPER_SNAKE (NvMDevErrorDetect -> NVM_DEV_ERROR_DETECT)."""
|
|
32
|
+
s = re.sub(r"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", "_", name)
|
|
33
|
+
return re.sub(r"_+", "_", s).upper()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Container:
|
|
38
|
+
"""One container value, merged across file splits (all fragments with the same AUTOSAR path)."""
|
|
39
|
+
path: str
|
|
40
|
+
name: str
|
|
41
|
+
defn: Optional[D.DefNode]
|
|
42
|
+
frags: list
|
|
43
|
+
module: "ModuleCfg"
|
|
44
|
+
parent: Optional["Container"] = None
|
|
45
|
+
children: list = field(default_factory=list)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def def_name(self) -> str:
|
|
49
|
+
return self.defn.name if self.defn else (self.frags[0].def_ref or "?").rsplit("/", 1)[-1]
|
|
50
|
+
|
|
51
|
+
def values(self, name: str) -> list:
|
|
52
|
+
return [pv for f in self.frags for pv in f.params if pv.name == name]
|
|
53
|
+
|
|
54
|
+
def value(self, name: str) -> Optional[ParamVal]:
|
|
55
|
+
v = self.values(name)
|
|
56
|
+
return v[0] if v else None
|
|
57
|
+
|
|
58
|
+
def text(self, name: str) -> Optional[str]:
|
|
59
|
+
v = self.value(name)
|
|
60
|
+
return v.value if v is not None else None
|
|
61
|
+
|
|
62
|
+
def sub(self, def_name: str) -> list:
|
|
63
|
+
return [c for c in self.children if c.def_name == def_name]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class ModuleCfg:
|
|
68
|
+
name: str # def short name (the Mip unless overridden)
|
|
69
|
+
elem: Elem
|
|
70
|
+
defn: Optional[D.DefNode]
|
|
71
|
+
variant: str
|
|
72
|
+
containers: list # all Containers, document order (pre-order)
|
|
73
|
+
top: list # top-level Containers
|
|
74
|
+
mip: str = ""
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def MIP(self) -> str:
|
|
78
|
+
return self.mip.upper()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class CgModel:
|
|
82
|
+
def __init__(self, project: Project, mip_overrides: Optional[dict] = None):
|
|
83
|
+
self.p = project
|
|
84
|
+
self.defs = project.defs
|
|
85
|
+
self.mip_overrides = mip_overrides or {}
|
|
86
|
+
self.modules: dict[str, ModuleCfg] = {}
|
|
87
|
+
self.by_path: dict[str, Container] = {}
|
|
88
|
+
self.order: dict[str, int] = {}
|
|
89
|
+
self._rev: Optional[dict] = None
|
|
90
|
+
self._sorted: dict = {}
|
|
91
|
+
for m in sorted(project.modules, key=lambda e: (e.file, e.line)):
|
|
92
|
+
self._add_module(m)
|
|
93
|
+
|
|
94
|
+
# ---------- construction ----------
|
|
95
|
+
def _def(self, def_ref: Optional[str]) -> Optional[D.DefNode]:
|
|
96
|
+
if not def_ref:
|
|
97
|
+
return None
|
|
98
|
+
d = self.defs.get(def_ref)
|
|
99
|
+
if d is not None:
|
|
100
|
+
return d
|
|
101
|
+
# vendor-relocated or other-release DEFINITION-REF: match by module-relative path
|
|
102
|
+
rel = self.defs.rel(def_ref)
|
|
103
|
+
for mp, md in self.defs.modules.items():
|
|
104
|
+
if rel.split("/", 1)[0] == md.name:
|
|
105
|
+
return self.defs.get(mp + rel[len(md.name):])
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def _add_module(self, m: Elem):
|
|
109
|
+
md = self._def(m.def_ref)
|
|
110
|
+
name = md.name if md else (m.def_ref or m.name).rsplit("/", 1)[-1]
|
|
111
|
+
if name in self.modules: # split module configuration: merge into the first one
|
|
112
|
+
mc = self.modules[name]
|
|
113
|
+
else:
|
|
114
|
+
variant = m.node.child_text("IMPLEMENTATION-CONFIG-VARIANT") or DEFAULT_VARIANT
|
|
115
|
+
mc = ModuleCfg(name, m, md, variant, [], [], self.mip_overrides.get(name, name))
|
|
116
|
+
self.modules[name] = mc
|
|
117
|
+
for c in m.children:
|
|
118
|
+
self._add_container(mc, c, None)
|
|
119
|
+
|
|
120
|
+
def _add_container(self, mc: ModuleCfg, e: Elem, parent: Optional[Container]):
|
|
121
|
+
c = self.by_path.get(e.path)
|
|
122
|
+
if c is None:
|
|
123
|
+
c = Container(e.path, e.name, self._def(e.def_ref), [], mc, parent)
|
|
124
|
+
self.by_path[e.path] = c
|
|
125
|
+
self.order[e.path] = len(self.order)
|
|
126
|
+
mc.containers.append(c)
|
|
127
|
+
(parent.children if parent else mc.top).append(c)
|
|
128
|
+
c.frags.append(e)
|
|
129
|
+
for ch in e.children:
|
|
130
|
+
self._add_container(mc, ch, c)
|
|
131
|
+
|
|
132
|
+
# ---------- queries ----------
|
|
133
|
+
def by_path_index(self, c: Container) -> int:
|
|
134
|
+
return self.order.get(c.path, 0)
|
|
135
|
+
|
|
136
|
+
def module(self, name: str) -> Optional[ModuleCfg]:
|
|
137
|
+
return self.modules.get(name)
|
|
138
|
+
|
|
139
|
+
def instances(self, mc: ModuleCfg, def_name: str) -> list:
|
|
140
|
+
return [c for c in mc.containers if c.def_name == def_name]
|
|
141
|
+
|
|
142
|
+
def target(self, pv: Optional[ParamVal]) -> Optional[Container]:
|
|
143
|
+
if pv is None or not pv.value:
|
|
144
|
+
return None
|
|
145
|
+
return self.by_path.get(pv.value.strip())
|
|
146
|
+
|
|
147
|
+
def reverse(self) -> dict:
|
|
148
|
+
"""target path -> [(Container, ref name)] for every ECUC reference value, document order."""
|
|
149
|
+
if self._rev is None:
|
|
150
|
+
self._rev = {}
|
|
151
|
+
for mc in self.modules.values():
|
|
152
|
+
for c in mc.containers:
|
|
153
|
+
for f in c.frags:
|
|
154
|
+
for pv in f.params:
|
|
155
|
+
if pv.is_ref and pv.value:
|
|
156
|
+
self._rev.setdefault(pv.value.strip(), []).append((c, pv.name))
|
|
157
|
+
return self._rev
|
|
158
|
+
|
|
159
|
+
def referrers(self, target: Container, module: Optional[str] = None, ref: Optional[str] = None) -> list:
|
|
160
|
+
out = []
|
|
161
|
+
for c, rname in self.reverse().get(target.path, []):
|
|
162
|
+
if module is not None and c.module.name != module:
|
|
163
|
+
continue
|
|
164
|
+
if ref is not None and rname != ref:
|
|
165
|
+
continue
|
|
166
|
+
out.append(c)
|
|
167
|
+
return out
|
|
168
|
+
|
|
169
|
+
def param_def(self, c: Container, name: str) -> Optional[D.DefNode]:
|
|
170
|
+
if c.defn is None:
|
|
171
|
+
return None
|
|
172
|
+
for p in c.defn.params + c.defn.refs:
|
|
173
|
+
if p.rsplit("/", 1)[-1] == name:
|
|
174
|
+
return self.defs.get(p)
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def eff_class(self, mc: ModuleCfg, d: Optional[D.DefNode]) -> int:
|
|
178
|
+
"""Effective config class rank of a param/ref (or container multiplicity) for the module's variant."""
|
|
179
|
+
if d is None:
|
|
180
|
+
return CLASS_RANK["PRE-COMPILE"]
|
|
181
|
+
cc = d.config_classes or d.mult_classes
|
|
182
|
+
if not cc:
|
|
183
|
+
return CLASS_RANK["PRE-COMPILE"]
|
|
184
|
+
cls = cc.get(mc.variant)
|
|
185
|
+
if cls is None: # variant not listed for this param: take the least dynamic class it supports
|
|
186
|
+
cls = min(cc.values(), key=lambda k: CLASS_RANK.get(k, 1))
|
|
187
|
+
return CLASS_RANK.get(cls, 1)
|
|
188
|
+
|
|
189
|
+
def symbolic_param(self, c: Container) -> Optional[D.DefNode]:
|
|
190
|
+
if c.defn is None:
|
|
191
|
+
return None
|
|
192
|
+
for p in c.defn.params:
|
|
193
|
+
d = self.defs.get(p)
|
|
194
|
+
if d is not None and d.symbolic_name:
|
|
195
|
+
return d
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
def symbol(self, c: Container) -> Optional[str]:
|
|
199
|
+
"""TPS_ECUC_02108: <Mip>Conf_<ContainerDefShortName>_<ContainerValueShortName>."""
|
|
200
|
+
if self.symbolic_param(c) is None:
|
|
201
|
+
return None
|
|
202
|
+
return f"{c.module.mip}Conf_{c.def_name}_{c.name}"
|
|
203
|
+
|
|
204
|
+
def symbol_value(self, c: Container) -> Optional[str]:
|
|
205
|
+
d = self.symbolic_param(c)
|
|
206
|
+
return c.text(d.name) if d is not None else None
|
|
207
|
+
|
|
208
|
+
def sorted_instances(self, mc: ModuleCfg, def_name: str) -> list:
|
|
209
|
+
"""Instances of a container def, ordered by their symbolic handle when they have one, else document order."""
|
|
210
|
+
key = (mc.name, def_name)
|
|
211
|
+
if key not in self._sorted:
|
|
212
|
+
insts = self.instances(mc, def_name)
|
|
213
|
+
if insts and self.symbolic_param(insts[0]) is not None:
|
|
214
|
+
def k(c):
|
|
215
|
+
v = parse_int(self.symbol_value(c) or "")
|
|
216
|
+
return (v is None, v if v is not None else 0)
|
|
217
|
+
insts = sorted(insts, key=k) # stable: ties stay in document order
|
|
218
|
+
self._sorted[key] = insts
|
|
219
|
+
return self._sorted[key]
|
|
220
|
+
|
|
221
|
+
def index_of(self, c: Container) -> int:
|
|
222
|
+
return self.sorted_instances(c.module, c.def_name).index(c)
|
|
223
|
+
|
|
224
|
+
def is_multi(self, c: Container) -> bool:
|
|
225
|
+
"""True if this container, or any ancestor, is an instance of a def with upper multiplicity > 1."""
|
|
226
|
+
x: Optional[Container] = c
|
|
227
|
+
while x is not None:
|
|
228
|
+
if x.defn is None or x.defn.upper != 1:
|
|
229
|
+
return True
|
|
230
|
+
x = x.parent
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
def release(self) -> tuple:
|
|
234
|
+
"""AUTOSAR release of the configuration, from the schema version of its files."""
|
|
235
|
+
for f in sorted(self.p.files):
|
|
236
|
+
head = self.p.files[f].data[:2048]
|
|
237
|
+
m = re.search(rb"AUTOSAR_(\d{5})\.xsd", head)
|
|
238
|
+
if m and m.group(1).decode() in SCHEMA_RELEASE:
|
|
239
|
+
return SCHEMA_RELEASE[m.group(1).decode()]
|
|
240
|
+
return (4, 11, 0)
|
|
241
|
+
|
|
242
|
+
def rel_files(self, mc: ModuleCfg) -> list:
|
|
243
|
+
files = sorted({f.file for c in mc.containers for f in c.frags} | {mc.elem.file})
|
|
244
|
+
return [self.p.rel(f).replace("\\", "/") for f in files]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def num(v: Optional[str]) -> Optional[int]:
|
|
248
|
+
return parse_int(v) if v is not None else None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def flt(v: Optional[str]) -> Optional[float]:
|
|
252
|
+
return parse_float(v) if v is not None else None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def iter_param_defs(defs: D.DefIndex, d: D.DefNode) -> Iterable[D.DefNode]:
|
|
256
|
+
for p in d.params + d.refs:
|
|
257
|
+
x = defs.get(p)
|
|
258
|
+
if x is not None:
|
|
259
|
+
yield x
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Module generators. Every .py file here registers itself with @register("<ModuleDefName>")."""
|
|
2
|
+
import importlib
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
for _f in sorted(os.listdir(os.path.dirname(__file__))):
|
|
6
|
+
if _f.endswith(".py") and _f != "__init__.py":
|
|
7
|
+
importlib.import_module(f"{__name__}.{_f[:-3]}")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""PduR and CanIf. Handle IDs, routing and PDU tables are def-driven; the neighbour handles
|
|
2
|
+
(PduR -> CanIf/Com, CanIf -> PduR) are found through the shared EcucPduCollection/Pdu
|
|
3
|
+
(TPS_ECUC 3.4.1.3) by the binding path language (`peer:`). ComStack_Types.h is the base header."""
|
|
4
|
+
from ..registry import Generator, register
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@register("PduR")
|
|
8
|
+
class PduR(Generator):
|
|
9
|
+
std_header = "ComStack_Types.h"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@register("CanIf")
|
|
13
|
+
class CanIf(Generator):
|
|
14
|
+
std_header = "ComStack_Types.h"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Dem: event IDs and the other symbolic IDs are generic (TPS_ECUC_02108). Module-specific:
|
|
2
|
+
* the DTC value of every DemDTC as DEM_DTC_<NAME> (DemDtcValue is not a symbolicNameValue param,
|
|
3
|
+
so TPS_ECUC_02108 does not name it; this generator's convention, <MIP>_<SN> per SWS_BSW_00124);
|
|
4
|
+
* Dem_MainFunction, scheduled with DemGeneral/DemTaskTime (ECUC_Dem_00715)."""
|
|
5
|
+
from .. import cwriter as W
|
|
6
|
+
from ..model import snake
|
|
7
|
+
from ..registry import Generator, register
|
|
8
|
+
from ...values import parse_int
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@register("Dem")
|
|
12
|
+
class Dem(Generator):
|
|
13
|
+
def specific(self, mo):
|
|
14
|
+
lines = []
|
|
15
|
+
for c in self.cg.instances(self.mc, "DemDTC"):
|
|
16
|
+
v = parse_int(c.text("DemDtcValue") or "")
|
|
17
|
+
if v is not None:
|
|
18
|
+
lines.append((f"DEM_DTC_{snake(c.name)}", W.c_uint(v, hexa=True), "DemDtcValue"))
|
|
19
|
+
mo.group("DTC values (DemDTC/DemDtcValue)", lines)
|
|
20
|
+
gen = [c for c in self.mc.containers if c.def_name == "DemGeneral"]
|
|
21
|
+
if gen and gen[0].text("DemTaskTime") is not None:
|
|
22
|
+
mo.main_functions = ["Dem_MainFunction"]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Port and Dio from the STANDARD R25-11 param defs, plus a per-silicon vendor-extension hook.
|
|
2
|
+
|
|
3
|
+
Standard part (no vendor data): Port_Cfg.h switches and PortConf_PortPin_* symbols, Port tables
|
|
4
|
+
(pin direction / initial mode / level / allowed modes bitmask / changeable flags) in the file of
|
|
5
|
+
their config class; Dio_Cfg.h with DioConf_DioPort_* / DioConf_DioChannel_* / DioConf_DioChannelGroup_*
|
|
6
|
+
symbols and the Dio_ChannelGroupType objects the group symbols point to (SWS_Dio_00184: mask,
|
|
7
|
+
offset, port; ECUC_Dio_00149: the symbol is the address expression).
|
|
8
|
+
|
|
9
|
+
Vendor hook: `.hydron/autosar.json` {"codegen": {"mcal": {"silicon": "tc4d", "pinMap": "mcal/pins.json"}}}
|
|
10
|
+
loads codegen/mcal/<silicon>.json (or a project file) that maps the standard pin attributes to
|
|
11
|
+
register fields, and a project pin map {"<PortPin SHORT-NAME>": {"port": "P00", "pin": 5,
|
|
12
|
+
"modes": {"PORT_PIN_MODE_CAN": 2}, "fields": {"PD": 0}}}. With both, Port_PBcfg.c / Port_Cfg.c gets
|
|
13
|
+
a register image (address, value) per pin. Missing data is reported, never guessed.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
from .. import cwriter as W
|
|
22
|
+
from ..model import CLASS_RANK
|
|
23
|
+
from ..registry import Generator, register
|
|
24
|
+
|
|
25
|
+
HERE = os.path.dirname(os.path.dirname(__file__))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _pins(spec: str) -> set:
|
|
29
|
+
out = set()
|
|
30
|
+
for part in str(spec).split(","):
|
|
31
|
+
a, _, b = part.strip().partition("-")
|
|
32
|
+
if a:
|
|
33
|
+
out.update(range(int(a), int(b or a) + 1))
|
|
34
|
+
return out
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def load_silicon(root: str, name: str) -> dict:
|
|
38
|
+
for f in (os.path.join(root, name), os.path.join(root, ".hydron", "codegen", "mcal", f"{name}.json"),
|
|
39
|
+
os.path.join(HERE, "mcal", f"{name}.json")):
|
|
40
|
+
if os.path.isfile(f):
|
|
41
|
+
with open(f) as fh:
|
|
42
|
+
return json.load(fh)
|
|
43
|
+
raise FileNotFoundError(f"no MCAL silicon mapping {name!r} (bundled: "
|
|
44
|
+
f"{', '.join(sorted(x[:-5] for x in os.listdir(os.path.join(HERE, 'mcal'))))})")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def register_image(sil: dict, pins: list, pin_map: dict) -> tuple:
|
|
48
|
+
"""[(address, value, comment)], [missing-data notes] for Port pins (container, attrs)."""
|
|
49
|
+
regs = sil["registers"]
|
|
50
|
+
fields = sil["fields"]
|
|
51
|
+
rows, missing = [], []
|
|
52
|
+
for c, attrs in pins:
|
|
53
|
+
pm = pin_map.get(c.name)
|
|
54
|
+
if pm is None:
|
|
55
|
+
missing.append(f"{c.name}: not in the pin map (port/pin unknown)")
|
|
56
|
+
continue
|
|
57
|
+
port = sil["ports"].get(pm.get("port"))
|
|
58
|
+
if port is None:
|
|
59
|
+
missing.append(f"{c.name}: port {pm.get('port')!r} not in the {sil['silicon']} mapping")
|
|
60
|
+
continue
|
|
61
|
+
pin = int(pm.get("pin", -1))
|
|
62
|
+
if pin not in _pins(port.get("pins", "0-15")):
|
|
63
|
+
missing.append(f"{c.name}: {pm['port']}.{pin} is not an available port slice ({sil.get('portsCite', '')})")
|
|
64
|
+
continue
|
|
65
|
+
vals: dict = {}
|
|
66
|
+
d = sil.get("direction", {}).get(attrs.get("PortPinDirection"))
|
|
67
|
+
if d is None:
|
|
68
|
+
missing.append(f"{c.name}: no field mapping for direction {attrs.get('PortPinDirection')}")
|
|
69
|
+
continue
|
|
70
|
+
vals.update({k: v for k, v in d.items() if k != "cite"})
|
|
71
|
+
mode = attrs.get("PortPinInitialMode")
|
|
72
|
+
mv = (pm.get("modes") or {}).get(mode)
|
|
73
|
+
if mv is not None:
|
|
74
|
+
vals["MODE"] = int(mv)
|
|
75
|
+
elif mode in sil.get("modes", {}):
|
|
76
|
+
vals.update({k: v for k, v in sil["modes"][mode].items() if k != "cite"})
|
|
77
|
+
else:
|
|
78
|
+
missing.append(f"{c.name}: alternate-function number for {mode} on {pm['port']}.{pin} "
|
|
79
|
+
"(per-pin, from the vendor VSMD / data sheet pin table)")
|
|
80
|
+
continue
|
|
81
|
+
for k, v in (pm.get("fields") or {}).items():
|
|
82
|
+
vals[k] = int(v)
|
|
83
|
+
level = sil.get("level", {}).get(attrs.get("PortPinLevelValue"), {})
|
|
84
|
+
if attrs.get("PortPinDirection") == "PORT_PIN_OUT":
|
|
85
|
+
vals.update(level)
|
|
86
|
+
by_reg: dict = {}
|
|
87
|
+
for k, v in vals.items():
|
|
88
|
+
f = fields.get(k)
|
|
89
|
+
if f is None:
|
|
90
|
+
missing.append(f"{c.name}: field {k} unknown in the {sil['silicon']} mapping")
|
|
91
|
+
continue
|
|
92
|
+
if v >> f["width"]:
|
|
93
|
+
missing.append(f"{c.name}: {k}={v} does not fit {f['width']} bit(s)")
|
|
94
|
+
continue
|
|
95
|
+
by_reg[f["reg"]] = by_reg.get(f["reg"], 0) | (int(v) << f["lsb"])
|
|
96
|
+
base = int(port["base"], 16)
|
|
97
|
+
# level request (GPIO) before the driver configuration (DRVCFG): no glitch when the output turns on
|
|
98
|
+
for reg in sorted(by_reg, key=lambda r: (r != "GPIO", r)):
|
|
99
|
+
r = regs[reg]
|
|
100
|
+
addr = base + int(r["offset"], 16) + pin * int(r["stride"], 16)
|
|
101
|
+
fl = ", ".join(f"{k}={v}" for k, v in sorted(vals.items()) if fields.get(k, {}).get("reg") == reg)
|
|
102
|
+
rows.append((addr, by_reg[reg], f"{c.name}: {pm['port']}.{pin} {reg} {fl}"))
|
|
103
|
+
return rows, missing
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@register("Port")
|
|
107
|
+
class Port(Generator):
|
|
108
|
+
def specific(self, mo):
|
|
109
|
+
opts = (getattr(self.ctx, "options", {}) or {}).get("mcal") or {}
|
|
110
|
+
if not opts.get("silicon"):
|
|
111
|
+
return
|
|
112
|
+
root = self.cg.p.root
|
|
113
|
+
sil = load_silicon(root, opts["silicon"])
|
|
114
|
+
pin_map = {}
|
|
115
|
+
if opts.get("pinMap"):
|
|
116
|
+
pf = opts["pinMap"] if os.path.isabs(opts["pinMap"]) else os.path.join(root, opts["pinMap"])
|
|
117
|
+
with open(pf) as fh:
|
|
118
|
+
pin_map = json.load(fh)
|
|
119
|
+
pins = []
|
|
120
|
+
rank = 1
|
|
121
|
+
for c in self.cg.sorted_instances(self.mc, "PortPin"):
|
|
122
|
+
attrs = {n: c.text(n) for n in ("PortPinDirection", "PortPinInitialMode", "PortPinLevelValue")}
|
|
123
|
+
pins.append((c, attrs))
|
|
124
|
+
for n in attrs:
|
|
125
|
+
rank = max(rank, self.cg.eff_class(self.mc, self.cg.param_def(c, n)))
|
|
126
|
+
rows, missing = register_image(sil, pins, pin_map)
|
|
127
|
+
mo.notes += [f"{sil['silicon']} ({sil.get('status', '')}): {m}" for m in missing]
|
|
128
|
+
if not rows:
|
|
129
|
+
mo.notes.append(f"{sil['silicon']}: no register image generated; needs vendor data: "
|
|
130
|
+
+ "; ".join(sil.get("needsVendorData", [])))
|
|
131
|
+
return
|
|
132
|
+
t = f"Port_Cfg{sil['silicon'].capitalize()}RegType"
|
|
133
|
+
mo.types.append("typedef struct {\n uint32 Address;\n uint32 Value;\n} " + t + ";")
|
|
134
|
+
cnt = f"PORT_CFG_NUM_{sil['silicon'].upper()}_REG_INIT"
|
|
135
|
+
mo.group(f"{sil['title']} ({sil.get('status')})", [(cnt, W.c_uint(len(rows)))])
|
|
136
|
+
decl = f"CONST({t}, PORT_CONST) Port_Cfg{sil['silicon'].capitalize()}RegInit[{cnt}]"
|
|
137
|
+
mo.hdecls.append(f"extern {decl};")
|
|
138
|
+
lines = [W.initializer([W.c_uint(a, True), W.c_uint(v, True)], cm) for a, v, cm in rows]
|
|
139
|
+
cites = "; ".join(f"{k}: {r['cite']}" for k, r in sil["registers"].items())
|
|
140
|
+
mo.table(rank, f"/* Register image from the {sil['silicon']} mapping ({cites}).\n"
|
|
141
|
+
f" * {sil.get('statusNote', '')[:300]} */\n{decl} = {{\n" + ",\n".join(lines) + "\n};")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@register("Dio")
|
|
145
|
+
class Dio(Generator):
|
|
146
|
+
def specific(self, mo):
|
|
147
|
+
if "Dio.h" not in self.ctx.headers:
|
|
148
|
+
mo.notes.append("no Dio.h (static Dio code) in the project: Dio_ChannelGroupType objects not generated")
|
|
149
|
+
return
|
|
150
|
+
objs, decls = [], []
|
|
151
|
+
for g in self.cg.instances(self.mc, "DioChannelGroup"):
|
|
152
|
+
ident = (g.text("DioChannelGroupIdentification") or "").strip()
|
|
153
|
+
m = re.fullmatch(r"&\s*([A-Za-z_]\w*)", ident)
|
|
154
|
+
if not m:
|
|
155
|
+
mo.notes.append(f"{g.path}: DioChannelGroupIdentification {ident!r} is not '&<object>'; "
|
|
156
|
+
"the Dio_ChannelGroupType object is not generated")
|
|
157
|
+
continue
|
|
158
|
+
port = g.parent.text("DioPortId") if g.parent is not None else None
|
|
159
|
+
vals = [W.c_uint(int(g.text("DioPortMask") or 0, 0), True), W.c_uint(int(g.text("DioPortOffset") or 0, 0)),
|
|
160
|
+
W.c_uint(int(port or 0, 0))]
|
|
161
|
+
decls.append(f"extern CONST(Dio_ChannelGroupType, DIO_CONST) {m.group(1)};")
|
|
162
|
+
objs.append(f"CONST(Dio_ChannelGroupType, DIO_CONST) {m.group(1)} = {{ {', '.join(vals)} }}; "
|
|
163
|
+
f"/* {g.name}: mask, offset, port (SWS_Dio_00184) */")
|
|
164
|
+
if objs:
|
|
165
|
+
mo.hdecls += ["/* Channel groups (ECUC_Dio_00149); Dio.h defines Dio_ChannelGroupType before including Dio_Cfg.h */",
|
|
166
|
+
*decls]
|
|
167
|
+
rank = max(self.cg.eff_class(self.mc, self.cg.param_def(g, "DioPortMask"))
|
|
168
|
+
for g in self.cg.instances(self.mc, "DioChannelGroup"))
|
|
169
|
+
mo.table(max(rank, CLASS_RANK["PRE-COMPILE"]), "\n".join(objs))
|
|
170
|
+
mo.include("Dio.h")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Memory stack: NvM, MemIf, Fee, Ea. Everything they need is expressible by the defs (block
|
|
2
|
+
descriptors, symbolic block IDs/numbers, pre-compile switches), so they are plain registrations;
|
|
3
|
+
the NvM block-descriptor layout of a given static NvM comes from its binding."""
|
|
4
|
+
from ..registry import Generator, register
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@register("NvM")
|
|
8
|
+
class NvM(Generator):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@register("MemIf")
|
|
13
|
+
class MemIf(Generator):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@register("Fee")
|
|
18
|
+
class Fee(Generator):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@register("Ea")
|
|
23
|
+
class Ea(Generator):
|
|
24
|
+
pass
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Generator registry. A module generator self-registers with @register("<ModuleDefName>").
|
|
2
|
+
|
|
3
|
+
Any configured module can be emitted by the generic Generator (macros, symbols, tables straight
|
|
4
|
+
from the param defs). A registered subclass adds only what the defs cannot express (and is what
|
|
5
|
+
`autosar_codegen` runs by default). To add a module: drop a file in codegen/modules/ with a
|
|
6
|
+
@register'ed subclass; modules/__init__.py imports every file in the folder.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from . import binding as B
|
|
14
|
+
from . import cwriter as W
|
|
15
|
+
from . import emit as E
|
|
16
|
+
|
|
17
|
+
GENERATORS: dict = {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register(name: str):
|
|
21
|
+
def deco(cls):
|
|
22
|
+
cls.module = name
|
|
23
|
+
GENERATORS[name] = cls
|
|
24
|
+
return cls
|
|
25
|
+
return deco
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def generator_for(name: str):
|
|
29
|
+
from . import modules # noqa: F401 (self-registration)
|
|
30
|
+
return GENERATORS.get(name, Generator)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def registered() -> list:
|
|
34
|
+
from . import modules # noqa: F401
|
|
35
|
+
return sorted(GENERATORS)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Generator:
|
|
39
|
+
module: Optional[str] = None
|
|
40
|
+
std_header = "Std_Types.h"
|
|
41
|
+
|
|
42
|
+
def __init__(self, ctx: E.Ctx, mc):
|
|
43
|
+
self.ctx = ctx
|
|
44
|
+
self.cg = ctx.cg
|
|
45
|
+
self.mc = mc
|
|
46
|
+
|
|
47
|
+
def run(self) -> E.ModuleOutput:
|
|
48
|
+
mo = E.ModuleOutput(self.ctx, self.mc)
|
|
49
|
+
mo.h_includes = [self.default_std_header()]
|
|
50
|
+
mb = B.select(self.ctx.bindings, self.mc.name, self.ctx.headers, self.ctx.binding_name)
|
|
51
|
+
mo.binding = mb
|
|
52
|
+
if mb and mb.get("headerIncludes"):
|
|
53
|
+
mo.h_includes = list(mb["headerIncludes"])
|
|
54
|
+
E.scalar_macros(self.cg, mo)
|
|
55
|
+
E.symbolic_names(self.cg, mo)
|
|
56
|
+
self.specific(mo)
|
|
57
|
+
if mb is None or mb.get("genericTables", False):
|
|
58
|
+
E.generic_tables(self.cg, mo, set(self.skip_tables(mo)))
|
|
59
|
+
if mb is not None:
|
|
60
|
+
apply_binding(self.cg, mo, mb)
|
|
61
|
+
return mo
|
|
62
|
+
|
|
63
|
+
def default_std_header(self) -> str:
|
|
64
|
+
"""ComStack_Types.h for modules that reference EcuC PDUs (communication stack), else Std_Types.h."""
|
|
65
|
+
md = self.mc.defn
|
|
66
|
+
if md is not None:
|
|
67
|
+
for d in self.cg.defs.by_path.values():
|
|
68
|
+
if d.module == md.path and d.kind and "REFERENCE" in d.kind and any(
|
|
69
|
+
r.endswith("/EcucPduCollection/Pdu") for r in d.dest_refs):
|
|
70
|
+
return "ComStack_Types.h"
|
|
71
|
+
return self.std_header
|
|
72
|
+
|
|
73
|
+
def skip_tables(self, mo) -> list:
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
def specific(self, mo: E.ModuleOutput):
|
|
77
|
+
"""Module-specific emission (only what the param defs cannot express)."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def apply_binding(cg, mo: E.ModuleOutput, mb: dict):
|
|
81
|
+
ev = B.Eval(cg, mo.mc, mo.ctx)
|
|
82
|
+
for inc in mb.get("sourceIncludes", []):
|
|
83
|
+
mo.include(inc)
|
|
84
|
+
macros = []
|
|
85
|
+
for m in mb.get("macros", []):
|
|
86
|
+
val = m["value"]
|
|
87
|
+
macros.append((m["name"], ev.macro(val) if re.match(r"^(count|max):", val) else val))
|
|
88
|
+
mo.group(f"Static-code interface ({mb['_binding']} binding)", macros)
|
|
89
|
+
ranks: dict = {}
|
|
90
|
+
pending: list = []
|
|
91
|
+
for t in mb.get("tables", []):
|
|
92
|
+
ev.rank = 1
|
|
93
|
+
rows = list(cg.sorted_instances(mo.mc, t["rows"]))
|
|
94
|
+
if t.get("order"):
|
|
95
|
+
from ..values import parse_int
|
|
96
|
+
rows.sort(key=lambda r: (parse_int(r.text(t["order"]) or "") is None, parse_int(r.text(t["order"]) or "") or 0))
|
|
97
|
+
lines, externs = [], []
|
|
98
|
+
for r in rows:
|
|
99
|
+
vals = []
|
|
100
|
+
for f in t["fields"]:
|
|
101
|
+
v, _ = ev.field(r, f)
|
|
102
|
+
vals.append(v)
|
|
103
|
+
if f.get("extern") and v not in ("NULL_PTR", "0", "0u") and not v.startswith('"'):
|
|
104
|
+
m = re.search(r"[A-Za-z_]\w*", v)
|
|
105
|
+
if m:
|
|
106
|
+
ext = "extern " + f["extern"].replace("{}", m.group(0)) + ";"
|
|
107
|
+
if ext not in externs:
|
|
108
|
+
externs.append(ext)
|
|
109
|
+
lead = f"[{cg.symbol(r)}] = " if t.get("designated") else ""
|
|
110
|
+
comment = t.get("comment", "name")
|
|
111
|
+
cm = {"name": r.name, "sym": cg.symbol(r) or r.name}.get(comment, r.name)
|
|
112
|
+
if t.get("scalar"):
|
|
113
|
+
lines.append(f" {lead}{vals[0]} /* {cm} */")
|
|
114
|
+
else:
|
|
115
|
+
lines.append(f" {lead}{{ {', '.join(vals)} }} /* {cm} */")
|
|
116
|
+
st = "static " if t.get("static") else ""
|
|
117
|
+
size = t.get("size", "")
|
|
118
|
+
body = (f"/* {t.get('doc', t['rows'])} */\n{st}CONST({t['type']}, {t['qualifier']}) {t['name']}[{size}] = {{\n" +
|
|
119
|
+
",\n".join(lines) + "\n};")
|
|
120
|
+
ranks[t["name"]] = ev.rank
|
|
121
|
+
pending.append((ev.rank, body, externs))
|
|
122
|
+
objects = mb.get("objects", [])
|
|
123
|
+
if objects:
|
|
124
|
+
# a root config object points at the (static) tables, so they all live in its file:
|
|
125
|
+
# the most dynamic config class among them (e.g. <Mip>_PBcfg.c for a post-build PduR_Config)
|
|
126
|
+
top = max([r for r, _, _ in pending] + [1])
|
|
127
|
+
pending = [(top, b, e) for _, b, e in pending]
|
|
128
|
+
for o in objects:
|
|
129
|
+
body = (f"CONST({o['type']}, {o['qualifier']}) {o['name']} = {{\n " + ", ".join(o["init"]) + "\n};")
|
|
130
|
+
pending.append((top, body, []))
|
|
131
|
+
for rank, body, externs in pending:
|
|
132
|
+
mo.table(rank, body, externs)
|
|
133
|
+
mo.used_mips |= ev.used_mips
|