@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,395 @@
1
+ """The shared BSW emitter: one ModuleOutput per module, rendered into the standard file set.
2
+
3
+ File placement follows each parameter's effective config class for the module's
4
+ IMPLEMENTATION-CONFIG-VARIANT (from the param defs' VALUE-CONFIG-CLASSES):
5
+ PRE-COMPILE / PUBLISHED-INFORMATION scalars -> #defines in <Mip>_Cfg.h
6
+ tables whose fields are all pre-compile -> <Mip>_Cfg.c (SWS_BSW_00103 Table 5.1)
7
+ tables with a link-time field -> <Mip>_Lcfg.c (SWS_BSW_00013)
8
+ tables with a post-build field -> <Mip>_PBcfg.c (SWS_BSW_00015)
9
+ Everything generic is driven by the defs: scalars of singleton containers become macros, every
10
+ symbolicNameValue parameter a TPS_ECUC_02108 symbol, every multiplicity-n container a const table.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import hashlib
15
+ from dataclasses import dataclass, field
16
+ from typing import Optional
17
+
18
+ from .. import ENGINE_VERSION
19
+ from .. import defs as D
20
+ from . import cwriter as W
21
+ from .model import CLASS_FILE, CLASS_RANK, CgModel, Container, ModuleCfg, snake
22
+
23
+
24
+ @dataclass
25
+ class Ctx:
26
+ cg: CgModel
27
+ headers: dict # project header basename -> path (static code, excluding the output dir)
28
+ bindings: dict
29
+ binding_name: Optional[str]
30
+ release: tuple
31
+ sources: list = field(default_factory=list) # project .c/.h files (static + in-house), for SchM scans
32
+ generating: set = field(default_factory=set) # module names generated in this run
33
+ available: set = field(default_factory=set) # header basenames reachable by the build (incl. the output dir)
34
+
35
+ def xsym(self, c: Container, here: ModuleCfg, used: set) -> str:
36
+ """Symbol of a container for use in module `here`: the TPS_ECUC_02108 name when its provider header
37
+ (<Mip>_Cfg.h) is generated now or already exists, else the configured value (named in a comment)."""
38
+ sym = self.cg.symbol(c)
39
+ if c.module is here:
40
+ return sym
41
+ if c.module.name in self.generating or f"{c.module.mip}_Cfg.h" in self.available:
42
+ used.add(c.module.mip)
43
+ return sym
44
+ from ..values import parse_int
45
+ v = parse_int(self.cg.symbol_value(c) or "")
46
+ return f"{W.c_uint(v)} /* {sym} */" if v is not None else sym
47
+
48
+
49
+ class ModuleOutput:
50
+ def __init__(self, ctx: Ctx, mc: ModuleCfg):
51
+ self.ctx = ctx
52
+ self.mc = mc
53
+ self.mip = mc.mip
54
+ self.MIP = mc.MIP
55
+ self.groups: list = [] # (title, [define lines])
56
+ self.types: list = []
57
+ self.hdecls: list = []
58
+ self.h_includes: list = []
59
+ self.tables: dict = {} # rank -> [text]
60
+ self.c_externs: dict = {} # rank -> [text]
61
+ self.c_includes: list = []
62
+ self.used_mips: set = set()
63
+ self.defined: set = set()
64
+ self.notes: list = []
65
+ self.extra_files: dict = {} # name -> text (module-specific additional files)
66
+ self.symbols: list = [] # (name, value, container path)
67
+
68
+ # ---------- collection ----------
69
+ def group(self, title: str, defs: list):
70
+ lines = []
71
+ for d in defs:
72
+ name = d[0]
73
+ if name in self.defined:
74
+ self.notes.append(f"duplicate #define {name} suppressed ({title})")
75
+ continue
76
+ self.defined.add(name)
77
+ lines.append(W.define(*d))
78
+ if lines:
79
+ self.groups.append((title, lines))
80
+
81
+ def table(self, rank: int, text: str, externs: Optional[list] = None):
82
+ rank = max(rank, 1)
83
+ self.tables.setdefault(rank, []).append(text)
84
+ for e in externs or []:
85
+ lst = self.c_externs.setdefault(rank, [])
86
+ if e not in lst:
87
+ lst.append(e)
88
+
89
+ def include(self, h: str):
90
+ if h not in self.c_includes:
91
+ self.c_includes.append(h)
92
+
93
+ # ---------- rendering ----------
94
+ def digest(self) -> str:
95
+ h = hashlib.sha256(ENGINE_VERSION.encode())
96
+ for f in sorted({f.file for c in self.mc.containers for f in c.frags} | {self.mc.elem.file}):
97
+ h.update(self.ctx.cg.p.files[f].data)
98
+ for f in sorted(self.ctx.cg.defs.files):
99
+ h.update(f.rsplit("/", 1)[-1].encode())
100
+ return "sha256:" + h.hexdigest()[:16]
101
+
102
+ def _banner(self, name: str) -> str:
103
+ return W.banner(name, self.mc.name, self.mc.variant, self.ctx.release, self.ctx.cg.rel_files(self.mc),
104
+ self._digest)
105
+
106
+ def render(self) -> dict:
107
+ self._digest = self.digest()
108
+ out = {}
109
+ hname = f"{self.mip}_Cfg.h"
110
+ g = W.guard(hname)
111
+ parts = [self._banner(hname), "", f"#ifndef {g}", f"#define {g}", ""]
112
+ for inc in self.h_includes:
113
+ parts.append(f'#include "{inc}"')
114
+ maj, mnr, rev = self.ctx.release
115
+ sw = [int(x) if x.isdigit() else 0 for x in ENGINE_VERSION.split("-")[0].split(".")][:3]
116
+ ver = [(f"{self.MIP}_CFG_AR_RELEASE_MAJOR_VERSION", f"{maj}u"),
117
+ (f"{self.MIP}_CFG_AR_RELEASE_MINOR_VERSION", f"{mnr}u"),
118
+ (f"{self.MIP}_CFG_AR_RELEASE_REVISION_VERSION", f"{rev}u"),
119
+ (f"{self.MIP}_CFG_SW_MAJOR_VERSION", f"{sw[0]}u"),
120
+ (f"{self.MIP}_CFG_SW_MINOR_VERSION", f"{sw[1]}u"),
121
+ (f"{self.MIP}_CFG_SW_PATCH_VERSION", f"{sw[2]}u")]
122
+ parts += ["", "/* Published information of this generated configuration (SWS_BSW_00059) */"]
123
+ parts += [W.define(n, v) for n, v in ver]
124
+ for title, lines in self.groups:
125
+ parts += ["", f"/* {title} */", *lines]
126
+ if self.types:
127
+ parts += ["", "/* Generated configuration types (one struct per multiplicity-n container def) */"]
128
+ parts += self.types
129
+ if self.hdecls:
130
+ parts += ["", *self.hdecls]
131
+ parts += ["", f"#endif /* {g} */", ""]
132
+ out[hname] = "\n".join(parts)
133
+ for rank in sorted(self.tables):
134
+ fname = f"{self.mip}_{CLASS_FILE[rank]}"
135
+ if fname in out: # PUBLISHED-INFORMATION and PRE-COMPILE share <Mip>_Cfg.c
136
+ continue
137
+ ranks = [r for r in self.tables if CLASS_FILE[r] == CLASS_FILE[rank]]
138
+ body = []
139
+ externs = []
140
+ for r in sorted(ranks):
141
+ body += self.tables[r]
142
+ externs += [e for e in self.c_externs.get(r, []) if e not in externs]
143
+ incs = list(self.c_includes) or [f"{self.mip}_Cfg.h"]
144
+ for m in sorted(self.used_mips - {self.mip}):
145
+ h = f"{m}_Cfg.h"
146
+ if h not in incs:
147
+ incs.append(h)
148
+ text = [self._banner(fname), ""]
149
+ text += [f'#include "{i}"' for i in incs]
150
+ text += ["", self._version_check(), ""]
151
+ if externs:
152
+ text += ["/* Objects owned by other modules / the application, referenced by this configuration */",
153
+ *externs, ""]
154
+ text += [W.memmap(self.mip, "\n\n".join(body)), ""]
155
+ out[fname] = "\n".join(text)
156
+ out.update(self.extra_files)
157
+ return out
158
+
159
+ def _version_check(self) -> str:
160
+ M = self.MIP
161
+ return "\n".join([
162
+ f"/* SWS_BSW_00036: the static code ({self.mip}.h) and this configuration must use the same AUTOSAR release */",
163
+ f"#if defined({M}_AR_RELEASE_MAJOR_VERSION) && defined({M}_AR_RELEASE_MINOR_VERSION)",
164
+ f"#if ({M}_AR_RELEASE_MAJOR_VERSION != {M}_CFG_AR_RELEASE_MAJOR_VERSION) || "
165
+ f"({M}_AR_RELEASE_MINOR_VERSION != {M}_CFG_AR_RELEASE_MINOR_VERSION)",
166
+ f'#error "{self.mip}: AUTOSAR release of the generated configuration does not match {self.mip}.h"',
167
+ "#endif",
168
+ "#endif",
169
+ ])
170
+
171
+
172
+ # ======================================================================================
173
+ # Generic emission, driven only by the param defs
174
+ # ======================================================================================
175
+
176
+ def strip_prefix(mip: str, name: str) -> str:
177
+ return name[len(mip):] if name.lower().startswith(mip.lower()) and len(name) > len(mip) else name
178
+
179
+
180
+ def scalar_macros(cg: CgModel, mo: ModuleOutput):
181
+ """PRE-COMPILE / PUBLISHED-INFORMATION params of singleton containers -> #define <MIP>_<PARAM>."""
182
+ mc = mo.mc
183
+ names: dict = {}
184
+ for c in mc.containers:
185
+ if cg.is_multi(c) or c.defn is None:
186
+ continue
187
+ lines = []
188
+ for pd_path in c.defn.params:
189
+ d = cg.defs.get(pd_path)
190
+ if d is None or d.upper != 1 or cg.eff_class(mc, d) > CLASS_RANK["PRE-COMPILE"]:
191
+ continue
192
+ raw = c.text(d.name)
193
+ if raw is None:
194
+ raw = d.default
195
+ if raw is None:
196
+ continue
197
+ base = f"{mo.MIP}_{snake(strip_prefix(mo.mip, d.name))}"
198
+ if base in names and names[base] != c.path:
199
+ base = f"{mo.MIP}_{snake(strip_prefix(mo.mip, c.def_name))}_{snake(strip_prefix(mo.mip, d.name))}"
200
+ names[base] = c.path
201
+ lines.append((base, W.macro_value(d, raw)))
202
+ if d.kind == "ENUMERATION":
203
+ enum_literals(mo, d)
204
+ mo.group(f"Pre-compile parameters of {c.def_name} ({c.path})", lines)
205
+
206
+
207
+ def enum_literals(mo: ModuleOutput, d: D.DefNode):
208
+ """Guarded values for module-prefixed enum literals used in #if comparisons (def literal order)."""
209
+ lits = [x for x in d.literals if x.upper().startswith(mo.MIP + "_")]
210
+ if not lits or len(lits) != len(d.literals):
211
+ return
212
+ key = f"__enum__{d.path}"
213
+ if key in mo.defined:
214
+ return
215
+ mo.defined.add(key)
216
+ lines = [f"#ifndef {lit}\n" + W.define(lit, W.c_uint(i)) + "\n#endif" for i, lit in enumerate(d.literals)]
217
+ mo.groups.append((f"Literals of {d.name} (def literal order; the static code may predefine them)", lines))
218
+
219
+
220
+ def symbolic_names(cg: CgModel, mo: ModuleOutput):
221
+ """TPS_ECUC_02108 / SWS_BSW_00200 symbols for every symbolicNameValue parameter of the module."""
222
+ by_def: dict = {}
223
+ for c in mo.mc.containers:
224
+ d = cg.symbolic_param(c)
225
+ if d is None:
226
+ continue
227
+ v = c.text(d.name)
228
+ if v is None:
229
+ continue
230
+ by_def.setdefault(c.def_name, []).append((c, d, v))
231
+ for dname, items in by_def.items():
232
+ lines = []
233
+ items = sorted(items, key=lambda t: (cg.sorted_instances(mo.mc, dname).index(t[0])))
234
+ for c, d, v in items:
235
+ sym = cg.symbol(c)
236
+ val = W.macro_value(d, v)
237
+ lines.append((sym, val))
238
+ mo.symbols.append((sym, val, c.path))
239
+ mo.group(f"Symbolic names of {dname} (TPS_ECUC_02108, {items[0][1].name})", lines)
240
+
241
+
242
+ # ---------- generic tables ----------
243
+
244
+ @dataclass
245
+ class Field:
246
+ name: str
247
+ ctype: str
248
+ steps: list # sub-container def names to the owner of the param
249
+ d: Optional[D.DefNode] = None # param/ref def
250
+ choice: Optional[D.DefNode] = None # for the choice selector field
251
+ parent: bool = False
252
+
253
+
254
+ def _fields(cg: CgModel, cdef: D.DefNode, prefix: str = "", steps: Optional[list] = None, depth: int = 0) -> list:
255
+ steps = steps or []
256
+ out: list = []
257
+ skipped: list = []
258
+ for p in cdef.params + cdef.refs:
259
+ d = cg.defs.get(p)
260
+ if d is None:
261
+ continue
262
+ if d.upper != 1:
263
+ if d.kind == "ENUMERATION" and 0 < len(d.literals) <= 32:
264
+ out.append(Field(prefix + d.name + "Mask", "uint32", steps, d)) # set of literals as a bitmask
265
+ else:
266
+ skipped.append(f"{d.name} (multi-valued)")
267
+ continue
268
+ if D.is_ref_kind(d.kind or ""):
269
+ if d.kind not in ("REFERENCE", "SYMBOLIC-NAME-REFERENCE", "CHOICE-REFERENCE"):
270
+ skipped.append(f"{d.name} ({d.kind.lower()})")
271
+ continue
272
+ out.append(Field(prefix + d.name, "uint16", steps, d))
273
+ continue
274
+ ct = W.param_ctype(d)
275
+ if ct is None:
276
+ skipped.append(f"{d.name} ({d.kind.lower()})")
277
+ continue
278
+ out.append(Field(prefix + d.name, ct, steps, d))
279
+ if depth < 4:
280
+ for sp in cdef.containers:
281
+ sd = cg.defs.get(sp)
282
+ if sd is None or sd.upper != 1:
283
+ continue # multiplicity-n sub-containers get their own table
284
+ if sd.tag == D.CHOICE_TAG:
285
+ out.append(Field(prefix + sd.name, "uint8", steps + [sd.name], choice=sd))
286
+ for bp in sd.containers:
287
+ bd = cg.defs.get(bp)
288
+ if bd is not None:
289
+ f2 = _fields(cg, bd, f"{prefix}{sd.name}_{bd.name}_", steps + [sd.name, bd.name], depth + 1)
290
+ out += f2
291
+ continue
292
+ out += _fields(cg, sd, f"{prefix}{sd.name}_", steps + [sd.name], depth + 1)
293
+ return out
294
+
295
+
296
+ def _owner(c: Optional[Container], steps: list) -> Optional[Container]:
297
+ for s in steps:
298
+ if c is None:
299
+ return None
300
+ subs = c.sub(s)
301
+ c = subs[0] if subs else None
302
+ return c
303
+
304
+
305
+ def _value(cg: CgModel, mo: ModuleOutput, row: Container, f: Field) -> tuple:
306
+ if f.parent:
307
+ p = row.parent
308
+ while p is not None and not cg.is_multi(p):
309
+ p = p.parent
310
+ return (W.c_uint(cg.index_of(p)) if p is not None else "0u", p.name if p is not None else "")
311
+ owner = _owner(row, f.steps)
312
+ if f.choice is not None:
313
+ if owner is None or not owner.children:
314
+ return ("0xFFu", "no choice")
315
+ br = owner.children[0]
316
+ names = [x.rsplit("/", 1)[-1] for x in f.choice.containers]
317
+ return (W.c_uint(names.index(br.def_name)) if br.def_name in names else "0xFFu", br.def_name)
318
+ d = f.d
319
+ if D.is_ref_kind(d.kind or ""):
320
+ t = cg.target(owner.value(d.name)) if owner is not None else None
321
+ if t is None:
322
+ return ("0xFFFFu", "no reference")
323
+ sym = cg.symbol(t)
324
+ if sym is not None:
325
+ return (mo.ctx.xsym(t, mo.mc, mo.used_mips), "")
326
+ if cg.is_multi(t):
327
+ return (W.c_uint(cg.index_of(t)), t.name)
328
+ return ("0u", t.name)
329
+ if d.upper != 1: # multi-valued enumeration -> bitmask of def literal indices
330
+ lits = [pv.value for pv in owner.values(d.name)] if owner is not None else []
331
+ mask = 0
332
+ for x in lits:
333
+ if x in d.literals:
334
+ mask |= 1 << d.literals.index(x)
335
+ return (W.c_uint(mask, hexa=True), "|".join(lits))
336
+ raw = owner.text(d.name) if owner is not None else None
337
+ return W.table_value(d, raw)
338
+
339
+
340
+ def generic_tables(cg: CgModel, mo: ModuleOutput, skip: set):
341
+ """One const struct array per multiplicity-n container def (plus singleton structs for non-PC scalars)."""
342
+ mc = mo.mc
343
+ seen: list = []
344
+ for c in mc.containers:
345
+ if c.def_name in skip or c.defn is None or c.def_name in seen:
346
+ continue
347
+ if c.defn.upper == 1 and not _has_dynamic(cg, mc, c):
348
+ continue
349
+ if c.defn.upper == 1 and cg.is_multi(c):
350
+ continue # flattened into its multi ancestor's row
351
+ seen.append(c.def_name)
352
+ for dname in seen:
353
+ rows = cg.sorted_instances(mc, dname)
354
+ cdef = rows[0].defn
355
+ fields = _fields(cg, cdef)
356
+ if rows[0].parent is not None and cg.is_multi(rows[0].parent):
357
+ fields = [Field("Parent", "uint16", [], parent=True)] + fields
358
+ if not fields:
359
+ continue
360
+ tname = f"{mo.mip}_Cfg{dname}Type"
361
+ oname = f"{mo.mip}_Cfg{dname}"
362
+ rank = max([cg.eff_class(mc, f.d) for f in fields if f.d is not None] + [cg.eff_class(mc, cdef), 1])
363
+ mo.types.append("typedef struct {\n" + "\n".join(f" {f.ctype} {f.name};" for f in fields) +
364
+ f"\n}} {tname};")
365
+ lines = []
366
+ for r in rows:
367
+ vals = []
368
+ notes = []
369
+ for f in fields:
370
+ v, n = _value(cg, mo, r, f)
371
+ vals.append(v)
372
+ sym = cg.symbol(r)
373
+ lines.append(W.initializer(vals, sym or r.name))
374
+ multi = cdef.upper != 1 or len(rows) > 1
375
+ cnt = f"{mo.MIP}_CFG_NUM_{snake(strip_prefix(mo.mip, dname))}"
376
+ if multi:
377
+ mo.group(f"Size of {oname}[]", [(cnt, W.c_uint(len(rows)))])
378
+ decl = f"CONST({tname}, {mo.MIP}_CONST) {oname}[{cnt}]"
379
+ body = f"{decl} = {{\n" + ",\n".join(lines) + "\n};"
380
+ else:
381
+ decl = f"CONST({tname}, {mo.MIP}_CONST) {oname}"
382
+ body = f"{decl} =\n" + lines[0].strip() + ";"
383
+ mo.hdecls.append(f"extern {decl};")
384
+ names = ", ".join(f.name for f in fields)
385
+ mo.table(rank, f"/* {dname}: {len(rows)} row(s), fields {names} */\n{body}")
386
+
387
+
388
+ def _has_dynamic(cg: CgModel, mc: ModuleCfg, c: Container) -> bool:
389
+ """A singleton container holding link-time/post-build scalars needs a const object, not macros."""
390
+ for p in c.defn.params:
391
+ d = cg.defs.get(p)
392
+ if d is not None and d.upper == 1 and W.param_ctype(d) and cg.eff_class(mc, d) > CLASS_RANK["PRE-COMPILE"]:
393
+ if c.text(d.name) is not None:
394
+ return True
395
+ return False
@@ -0,0 +1,89 @@
1
+ """Deterministic handle-ID assignment for symbolicNameValue parameters, written back in place.
2
+
3
+ TPS_ECUC_02107: values of symbolicNameValue parameters that the configuration editor or module
4
+ generator assigns are stored in the ECUC XML. TPS_ECUC_02108 turns them into the
5
+ `<Mip>Conf_<ContainerDef>_<ContainerValue>` #defines.
6
+
7
+ Algorithm (stable by construction):
8
+ * values already in the ECUC are kept as they are (so regeneration never renumbers a handle that
9
+ code or another ECU already uses), duplicates among them are reported, not "fixed";
10
+ * containers without a value get the lowest free integers >= max(def MIN, 0), in document order;
11
+ * the scope is one container def within one module (the symbol namespace of TPS_ECUC_02108).
12
+ Only INTEGER parameters are assigned. A missing STRING symbolic value is reported.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from typing import Optional
17
+
18
+ from ..values import parse_int
19
+ from .model import CgModel, ModuleCfg
20
+
21
+
22
+ def plan(cg: CgModel, modules: list) -> dict:
23
+ """-> {"assign": [{path, param, value, def}], "errors": [...], "notes": [...]}"""
24
+ out: dict = {"assign": [], "errors": [], "notes": []}
25
+ for name in modules:
26
+ mc = cg.module(name)
27
+ if mc is None:
28
+ continue
29
+ _plan_module(cg, mc, out)
30
+ return out
31
+
32
+
33
+ def _plan_module(cg: CgModel, mc: ModuleCfg, out: dict):
34
+ seen_defs: list = []
35
+ for c in mc.containers:
36
+ if c.def_name not in seen_defs:
37
+ seen_defs.append(c.def_name)
38
+ for dname in seen_defs:
39
+ insts = cg.instances(mc, dname)
40
+ d = cg.symbolic_param(insts[0])
41
+ if d is None:
42
+ continue
43
+ if d.kind != "INTEGER":
44
+ for c in insts:
45
+ if c.text(d.name) in (None, ""):
46
+ out["notes"].append(f"{c.path}: {d.name} (symbolic {d.kind}) has no value and cannot be "
47
+ "auto-assigned; its symbol is not generated")
48
+ continue
49
+ lo = max(parse_int(d.min) if d.min else 0, 0)
50
+ hi: Optional[int] = parse_int(d.max) if d.max else None
51
+ used: dict = {}
52
+ missing = []
53
+ for c in insts:
54
+ v = parse_int(c.text(d.name) or "")
55
+ if v is None:
56
+ missing.append(c)
57
+ continue
58
+ if v in used:
59
+ out["errors"].append(f"{d.name}={v} is used by both {used[v]} and {c.path} "
60
+ f"(handle IDs must be unique per module, TPS_ECUC_02108)")
61
+ used.setdefault(v, c.path)
62
+ nxt = lo
63
+ for c in missing:
64
+ while nxt in used:
65
+ nxt += 1
66
+ if hi is not None and nxt > hi:
67
+ out["errors"].append(f"{c.path}: no free {d.name} left in [{lo}..{hi}]")
68
+ break
69
+ used[nxt] = c.path
70
+ out["assign"].append({"path": c.path, "param": d.name, "value": nxt, "def": d.path})
71
+ if not d.with_auto:
72
+ out["notes"].append(f"{c.path}: assigned {d.name}={nxt}; the def has no withAuto, so check that "
73
+ "the number carries no hardware meaning")
74
+ nxt += 1
75
+
76
+
77
+ def apply(project, assignments: list, write: bool) -> dict:
78
+ """Write the assigned values into the ECUC ARXML in place (TPS_ECUC_02107) via the engine's editor.
79
+
80
+ write=False patches only the in-memory model (dry run), so the generated code still uses them.
81
+ """
82
+ if not assignments:
83
+ return {"written": False, "changes": []}
84
+ from ..edit import edit
85
+ ops = [{"op": "set", "path": a["path"], "param": a["param"], "value": a["value"]} for a in assignments]
86
+ res = edit(project, ops, dry_run=not write, run_validate=False)
87
+ if not res.get("ok"):
88
+ raise RuntimeError(f"handle-ID write-back failed: {res.get('error')}")
89
+ return {"written": bool(res.get("written")), "changes": res.get("changes", [])}
@@ -0,0 +1,60 @@
1
+ {
2
+ "silicon": "tc4d",
3
+ "title": "Infineon AURIX TC4Dx: Port pad configuration (register level)",
4
+ "status": "skeleton",
5
+ "statusNote": "Only public AURIX TC4Dx user manual data is in this file. It is enough to compute the GPIO direction / level / MODE=0 (GPIO) pad settings of a pin whose port and pin number are given in the project pin map. It is NOT a replacement for the Infineon MCAL: alternate-function MODE numbers per pin and mode literal, pad driver/level defaults, access-protection handling and the MCAL's own Port_ConfigType layout come from Infineon's vendor param defs (VSMD) / BSWMD and MCAL static code, which are under NDA and not used here.",
6
+ "sources": [
7
+ {"id": "UM", "doc": "AURIX TC4Dx user manual v1.1 (2025-06-26), public", "file": "tc4d/docs/infineon-infineon-aurix-tc4dx-um-v1.1-public-en_us.pdf-usermanual-en.pdf"}
8
+ ],
9
+ "registers": {
10
+ "GPIO": {"offset": "0x300", "stride": "0x10", "cite": "UM 47.4.2 Table 2432 (p. 8609) and 47.4.30 PADCFGp_GPIO (p. 8632-8633)"},
11
+ "DRVCFG": {"offset": "0x304", "stride": "0x10", "cite": "UM 47.4.2 Table 2432 (p. 8610) and 47.4.31 PADCFGp_DRVCFG (p. 8633-8637)"}
12
+ },
13
+ "fields": {
14
+ "DIR": {"reg": "DRVCFG", "lsb": 0, "width": 1, "cite": "UM p. 8634: 0B IN, 1B OUT"},
15
+ "OD": {"reg": "DRVCFG", "lsb": 1, "width": 1, "cite": "UM p. 8634: 0B push-pull, 1B open drain"},
16
+ "MODE": {"reg": "DRVCFG", "lsb": 4, "width": 4, "cite": "UM p. 8634: output: 0 = GPIO, n = alternate output function n; input: 0 tristate, 1 pull-down, 2 pull-up, 3 tristate"},
17
+ "PD": {"reg": "DRVCFG", "lsb": 8, "width": 3, "cite": "UM p. 8635: pad driver strength / edge"},
18
+ "PL": {"reg": "DRVCFG", "lsb": 12, "width": 3, "cite": "UM p. 8636: input level (AL / TTL)"},
19
+ "SET": {"reg": "GPIO", "lsb": 2, "width": 1, "cite": "UM p. 8633: write 1 drives the pin high (OUT is read-only)"},
20
+ "CLR": {"reg": "GPIO", "lsb": 3, "width": 1, "cite": "UM p. 8633: write 1 drives the pin low"}
21
+ },
22
+ "direction": {"PORT_PIN_IN": {"DIR": 0}, "PORT_PIN_OUT": {"DIR": 1}},
23
+ "level": {"PORT_PIN_LEVEL_HIGH": {"SET": 1}, "PORT_PIN_LEVEL_LOW": {"CLR": 1}},
24
+ "modes": {
25
+ "PORT_PIN_MODE_DIO": {"MODE": 0, "cite": "UM p. 8634: MODE 0 = GPIO (output) / tristate (input)"}
26
+ },
27
+ "modesNote": "Every other PortPinMode needs the alternate-function number of that pin (MODE = 1..15). Per-pin ALT numbers are listed in the TC4Dx data sheet pin tables, but the mapping from the vendor's PortPinMode literals to them is defined by Infineon's VSMD (NDA). Give them per pin in the project pin map ('modes': {'PORT_PIN_MODE_CAN': 2}).",
28
+ "ports": {
29
+ "P00": {"base": "0xF003A000", "pins": "0-15"},
30
+ "P01": {"base": "0xF003A400", "pins": "0-15"},
31
+ "P02": {"base": "0xF003A800", "pins": "0-15"},
32
+ "P03": {"base": "0xF003AC00", "pins": "0-15"},
33
+ "P04": {"base": "0xF003B000", "pins": "0-9"},
34
+ "P10": {"base": "0xF003C800", "pins": "0-11,13-15"},
35
+ "P13": {"base": "0xF003D400", "pins": "0-15"},
36
+ "P14": {"base": "0xF003D800", "pins": "0-15"},
37
+ "P15": {"base": "0xF003DC00", "pins": "0-8,10-15"},
38
+ "P16": {"base": "0xF003E000", "pins": "0-14"},
39
+ "P20": {"base": "0xF003F000", "pins": "0-3,6-14"},
40
+ "P21": {"base": "0xF003F400", "pins": "0-7"},
41
+ "P22": {"base": "0xF003F800", "pins": "0-11"},
42
+ "P23": {"base": "0xF003FC00", "pins": "0-7"},
43
+ "P25": {"base": "0xF0040400", "pins": "1"},
44
+ "P30": {"base": "0xF0041800", "pins": "0-15"},
45
+ "P31": {"base": "0xF0041C00", "pins": "6-7"},
46
+ "P32": {"base": "0xF0042000", "pins": "2,4-7"},
47
+ "P33": {"base": "0xF0042400", "pins": "0-15"},
48
+ "P34": {"base": "0xF0042800", "pins": "1-5"},
49
+ "P35": {"base": "0xF0042C00", "pins": "0-5"},
50
+ "P40": {"base": "0xF0044000", "pins": "0-14"}
51
+ },
52
+ "portsCite": "UM 47.8 Table 2448 'Registers address space - Pn' (p. 8650); available slices from Table 2446 'Port implementation in TC4DxAA-step' (p. 8645-8646)",
53
+ "needsVendorData": [
54
+ "Infineon MCAL Port/Dio vendor param defs (VSMD) and BSWMD: vendor PortPin parameters (driver strength, pad level, input select, alternate-function literals) and their config classes",
55
+ "the MCAL static code's Port_ConfigType / Dio config layout (what Port_Init actually consumes); without it only the register image below can be generated",
56
+ "per-pin alternate-function numbers for each PortPinMode literal (data sheet pin tables + the VSMD literal mapping)",
57
+ "write sequencing under the pad access protection (PADCFGp_ACCEN, ACCGRPy_*, PROTSE/Safety ENDINIT; UM 47.4.x) that the MCAL performs",
58
+ "PortPinId / DioChannelId encoding used by the Infineon MCAL (port*16+pin or otherwise); the project pin map gives port and pin explicitly instead"
59
+ ]
60
+ }