@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.
Files changed (49) hide show
  1. package/README.md +4 -0
  2. package/agents/autosar-integrator.md +34 -2
  3. package/package.json +4 -1
  4. package/plugins/index.js +53 -23
  5. package/python/ecuc_engine/__init__.py +1 -1
  6. package/python/ecuc_engine/add_signal.py +351 -0
  7. package/python/ecuc_engine/arxml_edit.py +387 -0
  8. package/python/ecuc_engine/audit.py +372 -69
  9. package/python/ecuc_engine/cli.py +119 -10
  10. package/python/ecuc_engine/codegen/__init__.py +185 -0
  11. package/python/ecuc_engine/codegen/binding.py +210 -0
  12. package/python/ecuc_engine/codegen/bindings/sip_standin.json +111 -0
  13. package/python/ecuc_engine/codegen/cdiff.py +395 -0
  14. package/python/ecuc_engine/codegen/cwriter.py +150 -0
  15. package/python/ecuc_engine/codegen/emit.py +395 -0
  16. package/python/ecuc_engine/codegen/handles.py +89 -0
  17. package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
  18. package/python/ecuc_engine/codegen/model.py +259 -0
  19. package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
  20. package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
  21. package/python/ecuc_engine/codegen/modules/dem.py +22 -0
  22. package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
  23. package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
  24. package/python/ecuc_engine/codegen/registry.py +133 -0
  25. package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
  26. package/python/ecuc_engine/context.py +481 -0
  27. package/python/ecuc_engine/csrc.py +189 -0
  28. package/python/ecuc_engine/defs.py +30 -0
  29. package/python/ecuc_engine/diff.py +13 -5
  30. package/python/ecuc_engine/find.py +162 -0
  31. package/python/ecuc_engine/gen.py +105 -0
  32. package/python/ecuc_engine/history.py +146 -0
  33. package/python/ecuc_engine/model.py +33 -29
  34. package/python/ecuc_engine/paths.py +221 -0
  35. package/python/ecuc_engine/probe.py +229 -0
  36. package/python/ecuc_engine/rte_api.py +935 -0
  37. package/python/ecuc_engine/rules/__init__.py +5 -3
  38. package/python/ecuc_engine/rules/mem.py +84 -0
  39. package/python/ecuc_engine/rules/nvm.py +131 -0
  40. package/python/ecuc_engine/rules/pdur.py +87 -0
  41. package/python/ecuc_engine/rules/system.py +162 -0
  42. package/python/ecuc_engine/sysmodel.py +152 -0
  43. package/python/ecuc_engine/validate.py +52 -3
  44. package/python/ecuc_engine/verify_rte.py +445 -0
  45. package/python/pyproject.toml +1 -1
  46. package/skills/autosar/SKILL.md +57 -1
  47. package/skills/autosar/references/codegen.md +30 -0
  48. package/skills/autosar/references/project-layout.md +11 -2
  49. 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