microchip-devtools 0.1.0__py3-none-any.whl
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.
- microchip_devtools/__init__.py +0 -0
- microchip_devtools/_project.py +14 -0
- microchip_devtools/format/__init__.py +0 -0
- microchip_devtools/format/uncrustify.py +119 -0
- microchip_devtools/list_cmds.py +31 -0
- microchip_devtools/mcc/__init__.py +0 -0
- microchip_devtools/mcc/check_peripheral.py +207 -0
- microchip_devtools/mcc/mcc_refresh.py +343 -0
- microchip_devtools/mcc/parse_hardware.py +374 -0
- microchip_devtools/setup_env/__init__.py +0 -0
- microchip_devtools/setup_env/_ui.py +63 -0
- microchip_devtools/setup_env/checks.py +178 -0
- microchip_devtools/setup_env/defaults.py +33 -0
- microchip_devtools/setup_env/runner.py +334 -0
- microchip_devtools/xc32/__init__.py +0 -0
- microchip_devtools/xc32/merge_hex.py +238 -0
- microchip_devtools/xc32/validate_fmt3.py +230 -0
- microchip_devtools-0.1.0.dist-info/METADATA +16 -0
- microchip_devtools-0.1.0.dist-info/RECORD +21 -0
- microchip_devtools-0.1.0.dist-info/WHEEL +4 -0
- microchip_devtools-0.1.0.dist-info/entry_points.txt +10 -0
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Project context helpers — read from environment vars set by Makefile.py."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def project_name() -> str:
|
|
8
|
+
"""Return project name from env var or current directory name."""
|
|
9
|
+
return os.environ.get("VOLTU_PROJECT_NAME", Path.cwd().name)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def project_root() -> Path:
|
|
13
|
+
"""Return project root from env var or current working directory."""
|
|
14
|
+
return Path(os.environ.get("VOLTU_PROJECT_ROOT", str(Path.cwd())))
|
|
File without changes
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
microchip_devtools.format.uncrustify — Python wrapper for uncrustify.
|
|
4
|
+
|
|
5
|
+
Walks one or more source roots, collects C/H files, runs uncrustify,
|
|
6
|
+
and reports which files were actually reformatted.
|
|
7
|
+
|
|
8
|
+
CLI usage:
|
|
9
|
+
mchp-format firmware/src -c uncrustifyVoltuC.cfg -x config mcc build
|
|
10
|
+
|
|
11
|
+
Python usage:
|
|
12
|
+
from microchip_devtools.format.uncrustify import format_files
|
|
13
|
+
rc = format_files(roots=["firmware/src"], config="style.cfg", exclude=["config"])
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
DEFAULT_EXTENSIONS: list[str] = [".c", ".h"]
|
|
22
|
+
DEFAULT_CONFIG: str = "uncrustifyVoltuC.cfg"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _collect_files(
|
|
26
|
+
roots: list[Path],
|
|
27
|
+
extensions: list[str],
|
|
28
|
+
exclude: list[str],
|
|
29
|
+
) -> list[Path]:
|
|
30
|
+
found: list[Path] = []
|
|
31
|
+
for root in roots:
|
|
32
|
+
if not root.is_dir():
|
|
33
|
+
print(f"uncrustify: warning: root not found: {root}", file=sys.stderr)
|
|
34
|
+
continue
|
|
35
|
+
for ext in extensions:
|
|
36
|
+
for path in sorted(root.rglob(f"*{ext}")):
|
|
37
|
+
rel = path.as_posix()
|
|
38
|
+
if not any(pat in rel for pat in exclude):
|
|
39
|
+
found.append(path)
|
|
40
|
+
return found
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _run_uncrustify(path: Path, config: str) -> tuple[bool, str]:
|
|
44
|
+
mtime_before = path.stat().st_mtime
|
|
45
|
+
result = subprocess.run(
|
|
46
|
+
[
|
|
47
|
+
"uncrustify",
|
|
48
|
+
"-c", config,
|
|
49
|
+
"--replace",
|
|
50
|
+
"--no-backup",
|
|
51
|
+
"--if-changed",
|
|
52
|
+
str(path),
|
|
53
|
+
],
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
)
|
|
57
|
+
if result.returncode != 0:
|
|
58
|
+
msg = (result.stderr or result.stdout).strip()
|
|
59
|
+
return False, msg
|
|
60
|
+
|
|
61
|
+
changed = path.stat().st_mtime != mtime_before
|
|
62
|
+
return changed, ""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def format_files(
|
|
66
|
+
roots: list[str | Path],
|
|
67
|
+
config: str = DEFAULT_CONFIG,
|
|
68
|
+
extensions: list[str] | None = None,
|
|
69
|
+
exclude: list[str] | None = None,
|
|
70
|
+
) -> int:
|
|
71
|
+
if extensions is None:
|
|
72
|
+
extensions = DEFAULT_EXTENSIONS
|
|
73
|
+
if exclude is None:
|
|
74
|
+
exclude = []
|
|
75
|
+
|
|
76
|
+
root_paths = [Path(r) for r in roots]
|
|
77
|
+
files = _collect_files(root_paths, extensions, exclude)
|
|
78
|
+
|
|
79
|
+
if not files:
|
|
80
|
+
print("uncrustify: no files matched — check roots and exclude patterns")
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
formatted: list[Path] = []
|
|
84
|
+
errors: list[tuple[Path, str]] = []
|
|
85
|
+
|
|
86
|
+
for path in files:
|
|
87
|
+
changed, err = _run_uncrustify(path, config)
|
|
88
|
+
if err:
|
|
89
|
+
errors.append((path, err))
|
|
90
|
+
elif changed:
|
|
91
|
+
formatted.append(path)
|
|
92
|
+
|
|
93
|
+
for f in formatted:
|
|
94
|
+
print(f" FM {f}")
|
|
95
|
+
|
|
96
|
+
if errors:
|
|
97
|
+
print(f"\n [{len(errors)} error(s)]", file=sys.stderr)
|
|
98
|
+
for f, msg in errors:
|
|
99
|
+
print(f" ERROR {f}\n {msg}", file=sys.stderr)
|
|
100
|
+
return 1
|
|
101
|
+
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def main() -> None:
|
|
106
|
+
parser = argparse.ArgumentParser(
|
|
107
|
+
description="Run uncrustify on a source tree and report reformatted files.",
|
|
108
|
+
)
|
|
109
|
+
parser.add_argument("roots", nargs="+", metavar="ROOT")
|
|
110
|
+
parser.add_argument("-c", "--config", default=DEFAULT_CONFIG, metavar="FILE")
|
|
111
|
+
parser.add_argument("-e", "--ext", nargs="+", default=DEFAULT_EXTENSIONS, metavar="EXT")
|
|
112
|
+
parser.add_argument("-x", "--exclude", nargs="+", default=[], metavar="PATTERN")
|
|
113
|
+
|
|
114
|
+
args = parser.parse_args()
|
|
115
|
+
sys.exit(format_files(args.roots, args.config, args.ext, args.exclude))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
main()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.table import Table
|
|
3
|
+
from rich.theme import Theme
|
|
4
|
+
|
|
5
|
+
_THEME = Theme(
|
|
6
|
+
{
|
|
7
|
+
"cmd": "bold cyan",
|
|
8
|
+
"desc": "default",
|
|
9
|
+
}
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
COMMANDS = [
|
|
13
|
+
("mchp-list", "List all available mchp commands"),
|
|
14
|
+
("mchp-setup", "Check prerequisites and install project deps"),
|
|
15
|
+
("mchp-xc32", "Detect XC32 fmt=3 compiler bug in ELF/object files"),
|
|
16
|
+
("mchp-hex", "Merge bootloader + app HEX into a single image"),
|
|
17
|
+
("mchp-fmt", "Format C/H source files with uncrustify"),
|
|
18
|
+
("mchp-mcc", "Force full MCC regeneration workflow"),
|
|
19
|
+
("mchp-periph", "Validate MCC-generated peripheral config files"),
|
|
20
|
+
("mchp-hw", "Show hardware config parsed from Harmony YML files"),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main():
|
|
25
|
+
console = Console(theme=_THEME, highlight=False)
|
|
26
|
+
table = Table(box=None, show_header=False, padding=(0, 2, 0, 0))
|
|
27
|
+
table.add_column(style="cmd", no_wrap=True)
|
|
28
|
+
table.add_column(style="desc")
|
|
29
|
+
for cmd, desc in COMMANDS:
|
|
30
|
+
table.add_row(cmd, desc)
|
|
31
|
+
console.print(table)
|
|
File without changes
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
microchip_devtools.mcc.check_peripheral — Validate MCC-generated peripheral config files.
|
|
4
|
+
|
|
5
|
+
Prevents two silent hardware bugs:
|
|
6
|
+
|
|
7
|
+
Bug 1 — MIPS interrupt vector stub missing in interrupts_a.S for a handler
|
|
8
|
+
declared in interrupts.c. Without a stub, the ISR never fires.
|
|
9
|
+
|
|
10
|
+
Bug 3 — Peripheral clock gated in PMD (plib_clk.c) while enabled in MCC
|
|
11
|
+
(core.yml). PMD registers are write-once per reset.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
mchp-check-peripheral [--root PATH] [--project-name NAME] [--stubs-file PATH]
|
|
15
|
+
|
|
16
|
+
Exit code:
|
|
17
|
+
0 — all checks passed
|
|
18
|
+
1 — one or more checks failed
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
from microchip_devtools._project import project_name as _env_project_name
|
|
28
|
+
from microchip_devtools._project import project_root as _env_project_root
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _read(path: Path) -> str:
|
|
32
|
+
try:
|
|
33
|
+
return path.read_text(encoding="utf-8")
|
|
34
|
+
except FileNotFoundError:
|
|
35
|
+
print(f"[ERROR] File not found: {path}", file=sys.stderr)
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _fail(msg: str) -> None:
|
|
40
|
+
print(f"[FAIL] {msg}", file=sys.stderr)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _ok(msg: str) -> None:
|
|
44
|
+
print(f"[OK] {msg}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _parse_handler_declarations(text: str) -> list[str]:
|
|
48
|
+
return re.findall(r'^void\s+(\w+_Handler)\s*\(void\)\s*;', text, re.MULTILINE)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _parse_extern_handlers(text: str) -> set[str]:
|
|
52
|
+
return set(re.findall(r'^\s+\.extern\s+(\w+_Handler)', text, re.MULTILINE))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check_vector_stubs(
|
|
56
|
+
interrupts_c_text: str,
|
|
57
|
+
interrupts_as_text: str,
|
|
58
|
+
known_missing: dict[str, str] | None = None,
|
|
59
|
+
) -> int:
|
|
60
|
+
"""Bug 1 check: every handler in interrupts.c must have a stub in interrupts_a.S."""
|
|
61
|
+
if known_missing is None:
|
|
62
|
+
known_missing = {}
|
|
63
|
+
|
|
64
|
+
declared = _parse_handler_declarations(interrupts_c_text)
|
|
65
|
+
externed = _parse_extern_handlers(interrupts_as_text)
|
|
66
|
+
|
|
67
|
+
print(f"[INFO] interrupts.c: {len(declared)} handler declaration(s)")
|
|
68
|
+
print(f"[INFO] interrupts_a.S: {len(externed)} .extern handler reference(s)\n")
|
|
69
|
+
|
|
70
|
+
failures = 0
|
|
71
|
+
for handler in sorted(declared):
|
|
72
|
+
if handler in known_missing:
|
|
73
|
+
print(f"[SKIP] {handler}: no stub (accepted — {known_missing[handler]})")
|
|
74
|
+
continue
|
|
75
|
+
if handler not in externed:
|
|
76
|
+
_fail(
|
|
77
|
+
f"{handler} is declared in interrupts.c "
|
|
78
|
+
f"but has NO dispatch stub in interrupts_a.S — "
|
|
79
|
+
f"this interrupt will NEVER fire."
|
|
80
|
+
)
|
|
81
|
+
failures += 1
|
|
82
|
+
else:
|
|
83
|
+
_ok(f"{handler}: stub present in interrupts_a.S")
|
|
84
|
+
return failures
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _parse_pmd_from_core_yml(text: str) -> dict[int, int]:
|
|
88
|
+
pmd_expected: dict[int, int] = {}
|
|
89
|
+
current_reg: int | None = None
|
|
90
|
+
for line in text.splitlines():
|
|
91
|
+
m = re.match(r'\s+PMD(\d+)_REG_VALUE:', line)
|
|
92
|
+
if m:
|
|
93
|
+
current_reg = int(m.group(1))
|
|
94
|
+
continue
|
|
95
|
+
if current_reg is not None:
|
|
96
|
+
mv = re.search(r"value:\s+'(\d+)'", line)
|
|
97
|
+
if mv:
|
|
98
|
+
pmd_expected[current_reg] = int(mv.group(1))
|
|
99
|
+
current_reg = None
|
|
100
|
+
continue
|
|
101
|
+
if re.match(r'\s{4}\w', line) and not re.match(r'\s{6,}', line):
|
|
102
|
+
current_reg = None
|
|
103
|
+
return pmd_expected
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _parse_pmd_from_plib_clk(text: str) -> dict[int, int]:
|
|
107
|
+
pmd_actual: dict[int, int] = {}
|
|
108
|
+
for m in re.finditer(r'\bPMD(\d+)\s*=\s*(0x[0-9a-fA-F]+)[Uu]?\s*;', text):
|
|
109
|
+
pmd_actual[int(m.group(1))] = int(m.group(2), 16)
|
|
110
|
+
return pmd_actual
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def check_pmd_registers(core_yml_text: str, plib_clk_text: str) -> int:
|
|
114
|
+
"""Bug 3 check: PMD register values in core.yml must match plib_clk.c."""
|
|
115
|
+
pmd_expected = _parse_pmd_from_core_yml(core_yml_text)
|
|
116
|
+
pmd_actual = _parse_pmd_from_plib_clk(plib_clk_text)
|
|
117
|
+
|
|
118
|
+
print(f"[INFO] core.yml: {len(pmd_expected)} PMD register(s) declared")
|
|
119
|
+
print(f"[INFO] plib_clk.c: {len(pmd_actual)} PMD register(s) found\n")
|
|
120
|
+
|
|
121
|
+
failures = 0
|
|
122
|
+
for reg in sorted(pmd_expected.keys()):
|
|
123
|
+
expected = pmd_expected[reg]
|
|
124
|
+
if reg not in pmd_actual:
|
|
125
|
+
_fail(
|
|
126
|
+
f"PMD{reg} is declared in core.yml "
|
|
127
|
+
f"but not found in plib_clk.c — cannot verify clock gate."
|
|
128
|
+
)
|
|
129
|
+
failures += 1
|
|
130
|
+
continue
|
|
131
|
+
actual = pmd_actual[reg]
|
|
132
|
+
if actual != expected:
|
|
133
|
+
diff = actual ^ expected
|
|
134
|
+
_fail(
|
|
135
|
+
f"PMD{reg} mismatch — "
|
|
136
|
+
f"plib_clk.c=0x{actual:08X} core.yml=0x{expected:08X} "
|
|
137
|
+
f"diff_bits=0x{diff:08X} "
|
|
138
|
+
f"(MCC regeneration likely reverted a manual clock-enable fix)"
|
|
139
|
+
)
|
|
140
|
+
failures += 1
|
|
141
|
+
else:
|
|
142
|
+
_ok(f"PMD{reg}: 0x{actual:08X} matches core.yml")
|
|
143
|
+
return failures
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def main() -> int:
|
|
147
|
+
parser = argparse.ArgumentParser(
|
|
148
|
+
description="Validate MCC-generated peripheral config files (Bug 1 + Bug 3)."
|
|
149
|
+
)
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"--root", type=Path, default=None,
|
|
152
|
+
help="Project root (default: $VOLTU_PROJECT_ROOT or cwd)",
|
|
153
|
+
)
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
"--project-name", default=None,
|
|
156
|
+
help="Project name (default: $VOLTU_PROJECT_NAME or folder name)",
|
|
157
|
+
)
|
|
158
|
+
parser.add_argument(
|
|
159
|
+
"--stubs-file", type=Path, default=None,
|
|
160
|
+
help="JSON file with known-missing stubs: {HandlerName: reason}",
|
|
161
|
+
)
|
|
162
|
+
args = parser.parse_args()
|
|
163
|
+
|
|
164
|
+
root = args.root or _env_project_root()
|
|
165
|
+
name = args.project_name or _env_project_name()
|
|
166
|
+
|
|
167
|
+
interrupts_c = root / "firmware/src/config/default/interrupts.c"
|
|
168
|
+
interrupts_as = root / "firmware/src/config/default/interrupts_a.S"
|
|
169
|
+
core_yml = root / f"firmware/{name}.X/{name}_default/components/core.yml"
|
|
170
|
+
plib_clk_c = root / "firmware/src/config/default/peripheral/clk/plib_clk.c"
|
|
171
|
+
|
|
172
|
+
known_missing: dict[str, str] = {}
|
|
173
|
+
if args.stubs_file and args.stubs_file.exists():
|
|
174
|
+
known_missing = json.loads(args.stubs_file.read_text(encoding="utf-8"))
|
|
175
|
+
else:
|
|
176
|
+
default_stubs = root / "pymake" / "check_peripheral_stubs.json"
|
|
177
|
+
if default_stubs.exists():
|
|
178
|
+
known_missing = json.loads(default_stubs.read_text(encoding="utf-8"))
|
|
179
|
+
|
|
180
|
+
print("--- Peripheral Configuration Check ---\n")
|
|
181
|
+
|
|
182
|
+
interrupts_c_text = _read(interrupts_c)
|
|
183
|
+
interrupts_as_text = _read(interrupts_as)
|
|
184
|
+
core_yml_text = _read(core_yml)
|
|
185
|
+
plib_clk_text = _read(plib_clk_c)
|
|
186
|
+
|
|
187
|
+
print("== Bug 1: Vector stubs (interrupts.c vs interrupts_a.S) ==")
|
|
188
|
+
failures = check_vector_stubs(interrupts_c_text, interrupts_as_text, known_missing)
|
|
189
|
+
|
|
190
|
+
print("\n== Bug 3: PMD clock gates (core.yml vs plib_clk.c) ==")
|
|
191
|
+
failures += check_pmd_registers(core_yml_text, plib_clk_text)
|
|
192
|
+
|
|
193
|
+
print()
|
|
194
|
+
if failures:
|
|
195
|
+
print(
|
|
196
|
+
f"[RESULT] FAILED — {failures} check(s) failed. "
|
|
197
|
+
f"Fix the generated files or re-run MCC.",
|
|
198
|
+
file=sys.stderr,
|
|
199
|
+
)
|
|
200
|
+
return 1
|
|
201
|
+
|
|
202
|
+
print("[RESULT] PASSED — all peripheral configuration checks OK.")
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
if __name__ == "__main__":
|
|
207
|
+
sys.exit(main())
|