Skip to content

Commit 042d12a

Browse files
svd: add a CLI with utility scripts
Add a CLI intended for various utility scripts. For now there is a single command that can be used to generate register content descriptions. Signed-off-by: Jonathan Nilsen <[email protected]>
1 parent 85e381a commit 042d12a

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ dependencies = [
1717
"numpy~=2.1",
1818
"typing_extensions>=4.4.0",
1919
]
20+
optional-dependencies = { cli = ["intelhex", "tomlkit"] }
21+
22+
[project.scripts]
23+
svada = "svd.__main__:cli"
2024

2125
[project.urls]
2226
homepage = "https://github.com/nordicsemiconductor/svada"

src/svd/__main__.py

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
#
2+
# Copyright (c) 2024 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
from __future__ import annotations
8+
9+
import argparse
10+
import dataclasses
11+
import enum
12+
import importlib.util
13+
import json
14+
import sys
15+
from pathlib import Path
16+
from textwrap import dedent
17+
18+
import svd
19+
from svd.util import BuildSelector, DeviceBuilder
20+
21+
_HAS_INTELHEX = importlib.util.find_spec("intelhex") is not None
22+
_HAS_TOMLKIT = importlib.util.find_spec("tomlkit") is not None
23+
24+
25+
class Format(enum.Enum):
26+
JSON = enum.auto()
27+
BIN = enum.auto()
28+
IHEX = enum.auto()
29+
TOML = enum.auto()
30+
31+
32+
def cli() -> None:
33+
top = argparse.ArgumentParser(
34+
description=dedent(
35+
"""\
36+
Collection of utility scripts for working with System View Description (SVD) files.
37+
38+
Use the --help option with each command to see more information about
39+
them and their individual options/arguments.
40+
"""
41+
),
42+
allow_abbrev=False,
43+
add_help=False, # To override -h
44+
)
45+
_add_help_option(top)
46+
47+
sub = top.add_subparsers(title="subcommands")
48+
49+
gen = sub.add_parser(
50+
"content-gen",
51+
help="Encode and decode device content to and from various formats.",
52+
description=dedent(
53+
"""\
54+
Encode device content from one of the supported formats and output it to another
55+
supported format.
56+
"""
57+
),
58+
allow_abbrev=False,
59+
add_help=False,
60+
)
61+
gen.set_defaults(_command="content-gen")
62+
_add_help_option(gen)
63+
64+
gen_in = gen.add_argument_group("input options")
65+
gen_in_mutex = gen_in.add_mutually_exclusive_group(required=True)
66+
gen_in_mutex.add_argument(
67+
"-j", "--in-json", action="store_true", help="Input is in JSON format."
68+
)
69+
if _HAS_INTELHEX:
70+
gen_in_mutex.add_argument(
71+
"-h",
72+
"--in-hex",
73+
action="store_true",
74+
help="Input is in Intel HEX format.",
75+
)
76+
if _HAS_TOMLKIT:
77+
gen_in_mutex.add_argument(
78+
"-t",
79+
"--in-toml",
80+
action="store_true",
81+
help="Input is in TOML format.",
82+
)
83+
gen_in.add_argument(
84+
"-i",
85+
"--input-file",
86+
type=Path,
87+
help="File to read the input from. If not given, stdin is used.",
88+
)
89+
90+
gen_svd = gen.add_argument_group("SVD options")
91+
gen_svd.add_argument(
92+
"-s",
93+
"--svd-file",
94+
required=True,
95+
type=Path,
96+
help="Path to the device SVD file.",
97+
)
98+
gen_svd.add_argument(
99+
"-n",
100+
"--no-strict",
101+
action="store_true",
102+
help="Don't enforce constraints on register and field values based on the SVD file.",
103+
)
104+
gen_svd.add_argument(
105+
"--svd-parse-options",
106+
type=json.loads,
107+
help=(
108+
"JSON object used to override fields in the Options object to customize svada parsing "
109+
"behavior. Mainly intended for advanced use cases such as working around "
110+
"difficult SVD files. "
111+
),
112+
)
113+
114+
gen_sel = gen.add_argument_group("selection options")
115+
gen_sel.add_argument(
116+
"-p",
117+
"--peripheral",
118+
metavar="NAME",
119+
dest="peripherals",
120+
action="append",
121+
help="Limit output content to the given peripheral. May be given multiple times.",
122+
)
123+
gen_sel.add_argument(
124+
"-a",
125+
"--address-range",
126+
metavar=("START", "END"),
127+
nargs=2,
128+
type=_parse_address_range,
129+
help="Limit output to a specific address range. Addresses can be given as hex or decimal.",
130+
)
131+
gen_sel.add_argument(
132+
"-c",
133+
"--content-status",
134+
choices=[c.value for c in BuildSelector.ContentStatus.__members__.values()],
135+
help="Limit output based on the status of the register content.",
136+
)
137+
138+
gen_out = gen.add_argument_group("output options")
139+
gen_out_mutex = gen_out.add_mutually_exclusive_group(required=True)
140+
gen_out_mutex.add_argument(
141+
"-J",
142+
"--out-json",
143+
action="store_true",
144+
help="Output in JSON format.",
145+
)
146+
gen_out_mutex.add_argument(
147+
"-B",
148+
"--out-bin",
149+
action="store_true",
150+
help="Output in binary format.",
151+
)
152+
if _HAS_INTELHEX:
153+
gen_out_mutex.add_argument(
154+
"-H",
155+
"--out-hex",
156+
action="store_true",
157+
help="Output in Intel HEX format.",
158+
)
159+
if _HAS_TOMLKIT:
160+
gen_out_mutex.add_argument(
161+
"-T",
162+
"--out-toml",
163+
action="store_true",
164+
help="Output in TOML format.",
165+
)
166+
gen_out.add_argument(
167+
"-o",
168+
"--output-file",
169+
type=Path,
170+
help="File to write the output to. If not given, output is written to stdout.",
171+
)
172+
173+
args = top.parse_args()
174+
if not hasattr(args, "_command"):
175+
top.print_usage()
176+
sys.exit(2)
177+
178+
if args._command == "content-gen":
179+
cmd_content_gen(args)
180+
else:
181+
top.print_usage()
182+
sys.exit(2)
183+
184+
sys.exit(0)
185+
186+
187+
def cmd_content_gen(args: argparse.Namespace) -> None:
188+
input_format = None
189+
input_mode = None
190+
if args.in_json:
191+
input_format = Format.JSON
192+
input_mode = "rb"
193+
elif _HAS_INTELHEX and getattr(args, "in_hex", False):
194+
input_format = Format.IHEX
195+
input_mode = "r"
196+
elif _HAS_TOMLKIT and getattr(args, "in_toml", False):
197+
input_format = Format.TOML
198+
input_mode = "rb"
199+
200+
assert input_format is not None
201+
assert input_mode is not None
202+
203+
if args.input_file:
204+
# TODO: encoding
205+
input_file = open(args.input_file, input_mode)
206+
else:
207+
input_file = sys.stdin.buffer if "b" in input_mode else sys.stdin
208+
209+
options = svd.Options(
210+
parent_relative_cluster_address=True,
211+
)
212+
if args.svd_parse_options:
213+
options = dataclasses.replace(options, **args.svd_parse_options)
214+
215+
device = svd.parse(args.svd_file, options=options)
216+
device_builder = DeviceBuilder(device, enforce_svd_constraints=not args.no_strict)
217+
218+
if input_format == Format.JSON:
219+
input_dict = json.load(input_file)
220+
device_builder.apply_dict(input_dict)
221+
elif input_format == Format.IHEX:
222+
from intelhex import IntelHex
223+
224+
ihex = IntelHex(input_file)
225+
ihex_memory = {a: ihex[a] for a in ihex.addresses()}
226+
device_builder.apply_memory(ihex_memory)
227+
elif input_format == Format.TOML:
228+
import tomlkit
229+
230+
input_dict = tomlkit.load(input_file).unwrap()
231+
device_builder.apply_dict(input_dict)
232+
233+
selector = BuildSelector(
234+
peripherals=args.peripherals if args.peripherals else None,
235+
address_range=args.address_range if args.address_range is not None else None,
236+
content_status=(
237+
BuildSelector.ContentStatus(args.content_status)
238+
if args.content_status
239+
else BuildSelector.ContentStatus.ANY
240+
),
241+
)
242+
243+
output_format = None
244+
output_mode = None
245+
if args.out_json:
246+
output_format = Format.JSON
247+
output_mode = "w"
248+
if args.out_bin:
249+
output_format = Format.BIN
250+
output_mode = "wb"
251+
elif _HAS_INTELHEX and getattr(args, "out_hex", False):
252+
output_format = Format.IHEX
253+
output_mode = "w"
254+
elif _HAS_TOMLKIT and getattr(args, "out_toml", False):
255+
output_format = Format.TOML
256+
output_mode = "w"
257+
258+
assert output_format is not None
259+
assert output_mode is not None
260+
261+
if args.output_file:
262+
output_file = open(args.output_file, output_mode, encoding="utf-8")
263+
else:
264+
output_file = sys.stdout.buffer if "b" in output_mode else sys.stdout
265+
266+
if output_format == Format.JSON:
267+
output_dict = device_builder.build_dict(selector)
268+
json.dump(output_dict, output_file)
269+
elif output_format == Format.BIN:
270+
output_bin = device_builder.build_bytes(selector)
271+
output_file.write(output_bin)
272+
elif output_format == Format.IHEX:
273+
from intelhex import IntelHex
274+
275+
output_ihex = IntelHex(device_builder.build_memory(selector))
276+
output_ihex.write_hex_file(output_file)
277+
elif output_format == Format.TOML:
278+
import tomlkit
279+
280+
output_dict = device_builder.build_dict(selector)
281+
tomlkit.dump(output_dict, output_file)
282+
283+
284+
def _add_help_option(parser: argparse.ArgumentParser) -> None:
285+
parser.add_argument("--help", action="help", help="Print help message")
286+
287+
288+
def _parse_address_range(addr_range: str) -> tuple[int, int]:
289+
start, end = [int(a.strip(), 0) for a in addr_range.split()]
290+
return start, end
291+
292+
293+
# Entry point when running with python -m svd
294+
if __name__ == "__main__":
295+
cli()

0 commit comments

Comments
 (0)