@h2loop/autosar 1.0.1 → 1.2.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 +11 -1
- package/agents/autosar-integrator.md +45 -2
- package/commands/autosar-trace.md +2 -2
- package/package.json +4 -1
- package/plugins/index.js +55 -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 +352 -50
- package/python/ecuc_engine/cli.py +125 -5
- package/python/ecuc_engine/codegen/__init__.py +233 -0
- package/python/ecuc_engine/codegen/binding.py +305 -0
- package/python/ecuc_engine/codegen/bindings/sip_standin.json +378 -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 +463 -0
- package/python/ecuc_engine/codegen/handles.py +176 -0
- package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
- package/python/ecuc_engine/codegen/model.py +264 -0
- package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
- package/python/ecuc_engine/codegen/modules/com.py +145 -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 +208 -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/ecucjson.py +75 -22
- package/python/ecuc_engine/find.py +164 -0
- package/python/ecuc_engine/gen.py +105 -0
- package/python/ecuc_engine/history.py +146 -0
- package/python/ecuc_engine/model.py +4 -1
- 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/specids.py +163 -0
- package/python/ecuc_engine/sysmodel.py +152 -0
- package/python/ecuc_engine/validate.py +15 -2
- package/python/ecuc_engine/values.py +26 -0
- package/python/ecuc_engine/verify_rte.py +445 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +78 -6
- package/skills/autosar/references/codegen.md +58 -0
- package/skills/autosar/references/ecuc-model.md +4 -1
- package/skills/autosar/references/project-layout.md +10 -1
- package/skills/autosar/references/runnable-code.md +50 -0
|
@@ -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,208 @@
|
|
|
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
|
+
try:
|
|
61
|
+
apply_binding(self.cg, mo, mb, self.path_steps(mo))
|
|
62
|
+
except B.BindingError as ex:
|
|
63
|
+
mo.errors.append(str(ex))
|
|
64
|
+
return mo
|
|
65
|
+
|
|
66
|
+
def path_steps(self, mo) -> dict:
|
|
67
|
+
"""call:<name> resolvers for the binding path language: {name: fn(container, Eval) -> token | None}."""
|
|
68
|
+
return {}
|
|
69
|
+
|
|
70
|
+
def default_std_header(self) -> str:
|
|
71
|
+
"""ComStack_Types.h for modules that reference EcuC PDUs (communication stack), else Std_Types.h."""
|
|
72
|
+
md = self.mc.defn
|
|
73
|
+
if md is not None:
|
|
74
|
+
for d in self.cg.defs.by_path.values():
|
|
75
|
+
if d.module == md.path and d.kind and "REFERENCE" in d.kind and any(
|
|
76
|
+
r.endswith("/EcucPduCollection/Pdu") for r in d.dest_refs):
|
|
77
|
+
return "ComStack_Types.h"
|
|
78
|
+
return self.std_header
|
|
79
|
+
|
|
80
|
+
def skip_tables(self, mo) -> list:
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
def specific(self, mo: E.ModuleOutput):
|
|
84
|
+
"""Module-specific emission (only what the param defs cannot express)."""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def apply_binding(cg, mo: E.ModuleOutput, mb: dict, ext: Optional[dict] = None):
|
|
88
|
+
ev = B.Eval(cg, mo.mc, mo.ctx)
|
|
89
|
+
ev.ext = ext or {}
|
|
90
|
+
sets = mb.get("sets", {})
|
|
91
|
+
for req in mb.get("require", []):
|
|
92
|
+
bad = [r for r in ev.rows(req["rows"], sets) if not ev.matches(r, [req])]
|
|
93
|
+
if bad:
|
|
94
|
+
vals = sorted({str(ev.raw(r, req["path"])) for r in bad})
|
|
95
|
+
mo.errors.append(f"{len(bad)} {req['rows']} container(s) have {req['path']} = {', '.join(vals)} "
|
|
96
|
+
f"({', '.join(r.name for r in bad[:5])}{', ...' if len(bad) > 5 else ''}); the "
|
|
97
|
+
f"{mb['_binding']} static code supports only "
|
|
98
|
+
f"{', '.join('(not configured)' if x is None else str(x) for x in req['in'])}: {req.get('why', '')}")
|
|
99
|
+
for un in mb.get("unsupported", []):
|
|
100
|
+
rows = cg.instances(mo.mc, un["rows"])
|
|
101
|
+
if rows:
|
|
102
|
+
mo.errors.append(f"{len(rows)} {un['rows']} container(s) configured ({', '.join(r.name for r in rows[:5])}"
|
|
103
|
+
f"{', ...' if len(rows) > 5 else ''}), which the {mb['_binding']} static code cannot "
|
|
104
|
+
f"represent: {un.get('why', '')}")
|
|
105
|
+
for inc in mb.get("sourceIncludes", []):
|
|
106
|
+
mo.include(inc)
|
|
107
|
+
macros = []
|
|
108
|
+
values: dict = {}
|
|
109
|
+
for m in mb.get("macros", []):
|
|
110
|
+
val = m["value"]
|
|
111
|
+
v = ev.macro(val, sets) if re.match(r"^(count|max):", val) else val
|
|
112
|
+
macros.append((m["name"], v))
|
|
113
|
+
values[m["name"]] = v
|
|
114
|
+
mo.group(f"Static-code interface ({mb['_binding']} binding)", macros)
|
|
115
|
+
pending: list = []
|
|
116
|
+
for vs in mb.get("vars", []):
|
|
117
|
+
ev.rank = 1
|
|
118
|
+
lines = []
|
|
119
|
+
for r in ev.rows(vs["rows"], sets):
|
|
120
|
+
size, _ = ev.field(r, {"name": vs["name"], "path": vs["size"]})
|
|
121
|
+
name = vs["name"].replace("{}", r.name)
|
|
122
|
+
lines.append(f"{'static ' if vs.get('static') else ''}VAR({vs['type']}, {vs['qualifier']}) {name}[{size}];")
|
|
123
|
+
pending.append(("var", ev.rank, vs.get("section", "VAR_NOINIT_UNSPECIFIED"), "\n".join(lines)))
|
|
124
|
+
for t in mb.get("tables", []):
|
|
125
|
+
ev.rank = 1
|
|
126
|
+
rows = ev.rows(t["rows"], sets)
|
|
127
|
+
if t.get("order"):
|
|
128
|
+
from ..values import parse_int
|
|
129
|
+
rows.sort(key=lambda r: (parse_int(r.text(t["order"]) or "") is None, parse_int(r.text(t["order"]) or "") or 0))
|
|
130
|
+
lines, externs = [], []
|
|
131
|
+
des = t.get("designated")
|
|
132
|
+
if des:
|
|
133
|
+
_check_index(cg, mo, t, rows, des, values)
|
|
134
|
+
for r in rows:
|
|
135
|
+
vals = []
|
|
136
|
+
for f in t["fields"]:
|
|
137
|
+
v, _ = ev.field(r, f)
|
|
138
|
+
vals.append(v)
|
|
139
|
+
if f.get("extern") and v not in ("NULL_PTR", "0", "0u") and not v.startswith('"'):
|
|
140
|
+
m = re.search(r"[A-Za-z_]\w*", v)
|
|
141
|
+
if m:
|
|
142
|
+
ext_ = "extern " + f["extern"].replace("{}", m.group(0)) + ";"
|
|
143
|
+
if ext_ not in externs:
|
|
144
|
+
externs.append(ext_)
|
|
145
|
+
lead = ""
|
|
146
|
+
if des:
|
|
147
|
+
lead = f"[{des.replace('{sym}', cg.symbol(r)) if isinstance(des, str) else cg.symbol(r)}] = "
|
|
148
|
+
comment = t.get("comment", "name")
|
|
149
|
+
cm = {"name": r.name, "sym": cg.symbol(r) or r.name}.get(comment, r.name)
|
|
150
|
+
if t.get("scalar"):
|
|
151
|
+
lines.append(f" {lead}{vals[0]} /* {cm} */")
|
|
152
|
+
else:
|
|
153
|
+
lines.append(f" {lead}{{ {', '.join(vals)} }} /* {cm} */")
|
|
154
|
+
st = "static " if t.get("static") else ""
|
|
155
|
+
size = t.get("size", "")
|
|
156
|
+
body = (f"/* {t.get('doc', t['rows'])} */\n{st}CONST({t['type']}, {t['qualifier']}) {t['name']}[{size}] = {{\n" +
|
|
157
|
+
",\n".join(lines) + "\n};")
|
|
158
|
+
pending.append(("table", ev.rank, body, externs))
|
|
159
|
+
objects = mb.get("objects", [])
|
|
160
|
+
if objects:
|
|
161
|
+
# a root config object points at the (static) tables and buffers, so they all live in its file:
|
|
162
|
+
# the most dynamic config class among them (e.g. <Mip>_PBcfg.c for a post-build PduR_Config)
|
|
163
|
+
top = max([p[1] for p in pending] + [1])
|
|
164
|
+
pending = [(p[0], top) + p[2:] for p in pending]
|
|
165
|
+
for o in objects:
|
|
166
|
+
body = (f"CONST({o['type']}, {o['qualifier']}) {o['name']} = {{\n " + ", ".join(o["init"]) + "\n};")
|
|
167
|
+
pending.append(("table", top, body, []))
|
|
168
|
+
for p in pending:
|
|
169
|
+
if p[0] == "var":
|
|
170
|
+
mo.var(p[1], p[2], p[3])
|
|
171
|
+
else:
|
|
172
|
+
mo.table(p[1], p[2], p[3])
|
|
173
|
+
mo.used_mips |= ev.used_mips
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _check_index(cg, mo, t: dict, rows: list, des, values: dict):
|
|
177
|
+
"""A designated table is indexed by handle (minus an offset): every row must land in [0, size) exactly once,
|
|
178
|
+
or the static code's table lookup would be wrong. Reported as an error, never renumbered here."""
|
|
179
|
+
from ..values import parse_int
|
|
180
|
+
off_name = None
|
|
181
|
+
if isinstance(des, str):
|
|
182
|
+
m = re.fullmatch(r"\{sym\}\s*-\s*(\w+)", des.strip())
|
|
183
|
+
if not m:
|
|
184
|
+
return
|
|
185
|
+
off_name = m.group(1)
|
|
186
|
+
num = lambda v: parse_int(re.sub(r"[uUlL]+$", "", str(v))) # noqa: E731
|
|
187
|
+
off = num(values.get(off_name, "0")) if off_name else 0
|
|
188
|
+
size = num(values[t["size"]]) if t.get("size") in values else len(rows)
|
|
189
|
+
seen: dict = {}
|
|
190
|
+
bad = 0
|
|
191
|
+
for r in rows:
|
|
192
|
+
h = parse_int(cg.symbol_value(r) or "")
|
|
193
|
+
if h is None:
|
|
194
|
+
continue
|
|
195
|
+
i = h - (off or 0)
|
|
196
|
+
if not 0 <= i < (size or 0) or i in seen:
|
|
197
|
+
bad += 1
|
|
198
|
+
if bad > 5:
|
|
199
|
+
continue
|
|
200
|
+
pm = cg.symbolic_param(r)
|
|
201
|
+
mo.errors.append(f"{r.path}: {pm.name if pm else 'handle'}={h} does not fit the static code's "
|
|
202
|
+
f"{t['name']}[{t.get('size')}] indexed by {des if isinstance(des, str) else '{sym}'} "
|
|
203
|
+
f"({'collides with ' + seen[i] if i in seen else f'index {i} outside 0..{(size or 0) - 1}'}); "
|
|
204
|
+
f"the {t['rows']} handles must be contiguous in the order the static code expects")
|
|
205
|
+
continue
|
|
206
|
+
seen[i] = r.name
|
|
207
|
+
if bad > 5:
|
|
208
|
+
mo.errors.append(f"... {bad - 5} more {t['rows']} handle(s) outside {t['name']}")
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""SchM_<Mip>.h and <Mip>_MemMap.h (+ an aggregating MemMap.h when the project has none).
|
|
2
|
+
|
|
3
|
+
SchM_<Mip>.h (SWS_Rte BSW scheduler part): exclusive-area enter/exit macros and the module's main
|
|
4
|
+
function prototypes.
|
|
5
|
+
* Exclusive areas: the ones the module's static code actually uses (`SchM_Enter_<Mip>_<EA>` found
|
|
6
|
+
in the project's non-generated C), plus any configured for the module in the Rte ECUC.
|
|
7
|
+
Mechanism per RteExclusiveAreaImplMechanism when configured (RteBswExclusiveAreaImpl), else
|
|
8
|
+
ALL_INTERRUPT_BLOCKING (OS SuspendAllInterrupts/ResumeAllInterrupts).
|
|
9
|
+
* Main functions: <Mip>_MainFunction for a `...MainFunctionPeriod` parameter, and one per instance
|
|
10
|
+
of a `<Mip>MainFunction<X>` container (Com-style named main functions).
|
|
11
|
+
<Mip>_MemMap.h: SWS_MemMap_00032 file name, keyword evaluation and START/STOP sequence checks in the
|
|
12
|
+
shape of the SWS_MemMap example (chapter A.3); pragmas are left to the project's MemMapAllocation
|
|
13
|
+
(`.hydron/autosar.json` codegen.memmap.pragmas), so the default build is compiler-neutral.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
from . import cwriter as W
|
|
20
|
+
|
|
21
|
+
DEFAULT_SECTIONS = ["CODE", "CONST_UNSPECIFIED", "CONFIG_DATA_UNSPECIFIED", "VAR_INIT_UNSPECIFIED",
|
|
22
|
+
"VAR_NOINIT_UNSPECIFIED", "VAR_CLEARED_UNSPECIFIED"]
|
|
23
|
+
|
|
24
|
+
MECHANISMS = {
|
|
25
|
+
"ALL_INTERRUPT_BLOCKING": ("SuspendAllInterrupts()", "ResumeAllInterrupts()"),
|
|
26
|
+
"OS_INTERRUPT_BLOCKING": ("SuspendOSInterrupts()", "ResumeOSInterrupts()"),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _scan(ctx, rx) -> dict:
|
|
31
|
+
hits: dict = {}
|
|
32
|
+
for f in ctx.sources:
|
|
33
|
+
try:
|
|
34
|
+
with open(f, errors="replace") as fh:
|
|
35
|
+
text = fh.read()
|
|
36
|
+
except OSError:
|
|
37
|
+
continue
|
|
38
|
+
for m in rx.finditer(text):
|
|
39
|
+
hits.setdefault(m.group(1), set()).add(ctx.cg.p.rel(f))
|
|
40
|
+
return hits
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _ea_mechanisms(cg) -> dict:
|
|
44
|
+
"""EA short name -> (mechanism, os resource) from the Rte ECUC, when configured."""
|
|
45
|
+
out = {}
|
|
46
|
+
rte = cg.module("Rte")
|
|
47
|
+
if rte is None:
|
|
48
|
+
return out
|
|
49
|
+
for c in rte.containers:
|
|
50
|
+
if c.def_name != "RteBswExclusiveAreaImpl":
|
|
51
|
+
continue
|
|
52
|
+
ref = c.text("RteBswExclusiveAreaRef") or ""
|
|
53
|
+
mech = c.text("RteExclusiveAreaImplMechanism") or "ALL_INTERRUPT_BLOCKING"
|
|
54
|
+
res = (c.text("RteBswExclusiveAreaOsResourceRef") or "").rsplit("/", 1)[-1]
|
|
55
|
+
if ref:
|
|
56
|
+
out[ref.rsplit("/", 1)[-1]] = (mech, res)
|
|
57
|
+
return out
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main_functions(cg, mo) -> list:
|
|
61
|
+
mc = mo.mc
|
|
62
|
+
out = list(getattr(mo, "main_functions", []))
|
|
63
|
+
for c in mc.containers:
|
|
64
|
+
if c.defn is None:
|
|
65
|
+
continue
|
|
66
|
+
m = re.match(rf"^{re.escape(mc.name)}MainFunction(\w*)$", c.def_name)
|
|
67
|
+
if m:
|
|
68
|
+
suffix = m.group(1)
|
|
69
|
+
fn = f"{mo.mip}_MainFunction{suffix}" + ("" if c.name == c.def_name else f"_{c.name}")
|
|
70
|
+
if fn not in out:
|
|
71
|
+
out.append(fn)
|
|
72
|
+
continue
|
|
73
|
+
if cg.is_multi(c):
|
|
74
|
+
continue
|
|
75
|
+
for p in c.defn.params:
|
|
76
|
+
if p.endswith("MainFunctionPeriod") and c.text(p.rsplit("/", 1)[-1]) is not None:
|
|
77
|
+
fn = f"{mo.mip}_MainFunction"
|
|
78
|
+
if fn not in out:
|
|
79
|
+
out.append(fn)
|
|
80
|
+
return out
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def schm_header(ctx, mo) -> tuple:
|
|
84
|
+
mip, MIP = mo.mip, mo.MIP
|
|
85
|
+
eas = _scan(ctx, re.compile(rf"\bSchM_(?:Enter|Exit)_{re.escape(mip)}_(\w+)\s*\("))
|
|
86
|
+
mech = _ea_mechanisms(ctx.cg)
|
|
87
|
+
for ea in mech:
|
|
88
|
+
if ea.upper().startswith(MIP + "_") and ea not in eas:
|
|
89
|
+
eas[ea] = {"Rte ECUC"}
|
|
90
|
+
mfs = main_functions(ctx.cg, mo)
|
|
91
|
+
name = f"SchM_{mip}.h"
|
|
92
|
+
g = W.guard(name)
|
|
93
|
+
lines = [mo._banner(name), "", f"#ifndef {g}", f"#define {g}", "", '#include "Std_Types.h"']
|
|
94
|
+
if eas:
|
|
95
|
+
lines.append('#include "Os.h"')
|
|
96
|
+
if eas:
|
|
97
|
+
lines += ["", "/* Exclusive areas (SWS_Rte SchM_Enter/SchM_Exit); used in: "
|
|
98
|
+
+ ", ".join(sorted({f for s in eas.values() for f in s})) + " */"]
|
|
99
|
+
for ea in sorted(eas):
|
|
100
|
+
m, res = mech.get(ea, ("ALL_INTERRUPT_BLOCKING", ""))
|
|
101
|
+
if m == "OS_RESOURCE" and res:
|
|
102
|
+
enter, exit_ = f"(void)GetResource({res})", f"(void)ReleaseResource({res})"
|
|
103
|
+
else:
|
|
104
|
+
enter, exit_ = MECHANISMS.get(m, ("do { } while (0)", "do { } while (0)"))
|
|
105
|
+
lines += [W.define(f"SchM_Enter_{mip}_{ea}()", enter, m), W.define(f"SchM_Exit_{mip}_{ea}()", exit_)]
|
|
106
|
+
if mfs:
|
|
107
|
+
lines += ["", "/* Main functions called by the BSW scheduler */"]
|
|
108
|
+
lines += [f"extern FUNC(void, {MIP}_CODE) {fn}(void);" for fn in mfs]
|
|
109
|
+
lines += ["", f"#endif /* {g} */", ""]
|
|
110
|
+
return name, "\n".join(lines)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def module_memmap(ctx, mo, pragmas: dict) -> tuple:
|
|
114
|
+
mip, MIP = mo.mip, mo.MIP
|
|
115
|
+
used = _scan(ctx, re.compile(rf"\b{re.escape(MIP)}_START_SEC_(\w+)"))
|
|
116
|
+
sections = list(DEFAULT_SECTIONS) + sorted(s for s in used if s not in DEFAULT_SECTIONS)
|
|
117
|
+
name = f"{mip}_MemMap.h"
|
|
118
|
+
lines = [mo._banner(name),
|
|
119
|
+
"/* Memory mapping of the module's sections (SWS_MemMap_00032). No include guard: it is included",
|
|
120
|
+
" * once per section switch. Pragmas come from codegen.memmap.pragmas (MemMapAllocation); none by default. */",
|
|
121
|
+
"", "#define MEMMAP_ERROR", ""]
|
|
122
|
+
for i, sec in enumerate(sections):
|
|
123
|
+
kw = f"{MIP}_SEC_{sec}"
|
|
124
|
+
start = pragmas.get(sec, {}).get("start", "") if isinstance(pragmas.get(sec), dict) else ""
|
|
125
|
+
stop = pragmas.get(sec, {}).get("stop", "") if isinstance(pragmas.get(sec), dict) else ""
|
|
126
|
+
lines += [
|
|
127
|
+
f"{'#if' if i == 0 else '#elif'} defined({MIP}_START_SEC_{sec})",
|
|
128
|
+
f" #undef {MIP}_START_SEC_{sec}",
|
|
129
|
+
" #undef MEMMAP_ERROR",
|
|
130
|
+
f" #ifdef {MIP}_MEMMAP_SEQUENCE_OPEN",
|
|
131
|
+
f' #error "{name}: {kw}: STOP the open section before starting a new one"',
|
|
132
|
+
" #endif",
|
|
133
|
+
f" #define {MIP}_MEMMAP_SEQUENCE_OPEN",
|
|
134
|
+
*( [f" {start}"] if start else []),
|
|
135
|
+
f"#elif defined({MIP}_STOP_SEC_{sec})",
|
|
136
|
+
f" #undef {MIP}_STOP_SEC_{sec}",
|
|
137
|
+
" #undef MEMMAP_ERROR",
|
|
138
|
+
f" #ifndef {MIP}_MEMMAP_SEQUENCE_OPEN",
|
|
139
|
+
f' #error "{name}: {kw}: STOP without START"',
|
|
140
|
+
" #endif",
|
|
141
|
+
f" #undef {MIP}_MEMMAP_SEQUENCE_OPEN",
|
|
142
|
+
*( [f" {stop}"] if stop else []),
|
|
143
|
+
]
|
|
144
|
+
lines += ["#endif", "", "#if defined(MEMMAP_ERROR)", f' #error "{name}: unknown memory section"', "#endif", ""]
|
|
145
|
+
return name, "\n".join(lines), sections
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def aggregate_memmap(ctx, entries: list) -> str:
|
|
149
|
+
"""Legacy MemMap.h dispatching to the per-module files (only emitted when the project has none)."""
|
|
150
|
+
lines = [W.banner("MemMap.h", "MemMap", "-", ctx.release, [], "-"),
|
|
151
|
+
"/* Dispatches each <MIP>_START_SEC_/<MIP>_STOP_SEC_ keyword to <Mip>_MemMap.h. No include guard. */", ""]
|
|
152
|
+
first = True
|
|
153
|
+
for mip, sections in entries:
|
|
154
|
+
MIP = mip.upper()
|
|
155
|
+
cond = " || \\\n ".join(f"defined({MIP}_{k}_SEC_{s})" for s in sections for k in ("START", "STOP"))
|
|
156
|
+
lines += [f"{'#if' if first else '#elif'} {cond}", f' #include "{mip}_MemMap.h"']
|
|
157
|
+
first = False
|
|
158
|
+
lines += ["#else", ' #error "MemMap.h: unknown memory section"', "#endif", ""]
|
|
159
|
+
return "\n".join(lines)
|