cpu/stm32/dist: add generator for kconfig cpu lines and models

This commit is contained in:
Alexandre Abadie 2020-10-01 14:09:36 +02:00
parent a83927d031
commit 06c3361a15
No known key found for this signature in database
GPG Key ID: 1C919A403CAE1405
5 changed files with 274 additions and 0 deletions

17
cpu/stm32/dist/kconfig/Kconfig.lines.j2 vendored Normal file
View File

@ -0,0 +1,17 @@
# Copyright (c) 2020 Inria
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
#
# This file was auto-generated from ST ProductsList.xlsx sheet using the
# script in cpu/stm32/dist/kconfig/gen_kconfig.py
# See cpu/stm32/dist/kconfig/README.md for details
# CPU lines
{%- for line in lines %}
config CPU_LINE_{{ line }}
bool
select CPU_FAM_{{ fam | upper }}
{% endfor -%}

View File

@ -0,0 +1,23 @@
# Copyright (c) 2020 Inria
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
#
# This file was auto-generated from ST ProductsList.xlsx sheet using the
# script in cpu/stm32/dist/kconfig/gen_kconfig.py
# See cpu/stm32/dist/kconfig/README.md for details
# CPU models
{%- for item in models %}
config CPU_MODEL_{{ item.model }}
bool
select {{ item.line | upper }}
{% endfor %}
# Configure CPU model
config CPU_MODEL
{%- for item in models %}
default "{{ item.model | lower }}" if CPU_MODEL_{{ item.model }}
{%- endfor %}

78
cpu/stm32/dist/kconfig/README.md vendored Normal file
View File

@ -0,0 +1,78 @@
## STM32 CPU lines and models Kconfig generator
The script `gen_kconfig.py` can be used to automatically generate the Kconfig
files describing CPU lines and models, per families (f0, f1, etc) and from
the ProductsList.xlsx sheet that are downloadable on the ST website.
### Prepare the data
The sheet are available from
https://www.st.com/en/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus.html.
Just select a CPU series (e.g family in RIOT) in the menu on the left and go in
the product selector tab, then click the `Export` button to download the Excel
sheet (the default filename is `ProductsList.xlsx`).
The available CPU lines are extracted from the
`cpu/stm32/include/vendor/cmsis/<fam>/Include` directory. This means that the
headers of a given family are already fetched here. This can be done with the
following `make` commands:
```
$ cd $RIOTBASE
$ RIOTBASE=$(pwd) RIOTTOOLS=$(pwd)/dist/tools CPU_FAM=<cpu_fam> make -C cpu/stm32/include/vendor
```
`<cpu_fam>` can be any family in `f0`, `f1`, etc
### `gen_kconfig.py` dependencies
The script depends on `jinja2` templating engine to generate the Kconfig files
and `xlrd` to load and parse the Excel sheets. The dependencies can be
installed with `pip`:
```
$ pip install -r ./cpu/stm32/dist/kconfig/requirements.txt
```
### `gen_kconfig.py` usage
The script can be used to generate the `Kconfig.lines` and `Kconfig.models` of
a given family as follows:
```
$ cd $RIOTBASE
$ ./cpu/stm32/dist/kconfig/gen_kconfig.py <cpu_fam> --sheets <path-to-sheet>/ProductsList.xlsx
```
The `--sheets` option can take several files. This allows to handle the L4 case
where the list of models is available in 2 separate sheets. So for L4 family,
the command should be:
```
$ cd $RIOTBASE
$ ./cpu/stm32/dist/kconfig/gen_kconfig.py l4 --sheets <path-to-sheet>/L4ProductsList.xlsx <path-to-sheet>/L4+ProductsList.xlsx
```
By default, if the `Kconfig.lines` and `Kconfig.models` files of a given family
were not already created, they are created.
If the `Kconfig.lines` and `Kconfig.models` files of a given family are already
available in RIOT, by default the script will just dump the content to stdout.
The files can still be overwritten by using the `--overwrite` flag.
Print the detailed usage with `--help`:
```
$ ./cpu/stm32/dist/kconfig/gen_kconfig.py --help
usage: gen_kconfig.py [-h] [--sheets SHEETS [SHEETS ...]] [--overwrite] [--quiet] cpu_fam
positional arguments:
cpu_fam STM32 CPU Family
optional arguments:
-h, --help show this help message and exit
--sheets SHEETS [SHEETS ...], -s SHEETS [SHEETS ...]
Excel sheet containing the list of products
--overwrite, -o Overwrite any existing Kconfig file
--quiet, -q Be quiet
```

154
cpu/stm32/dist/kconfig/gen_kconfig.py vendored Executable file
View File

@ -0,0 +1,154 @@
#!/usr/bin/env python3
# Copyright (C) 2020 Inria
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
import os
import argparse
import xlrd
from jinja2 import FileSystemLoader, Environment
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
RIOTBASE = os.getenv(
"RIOTBASE", os.path.abspath(os.path.join(CURRENT_DIR, "../../../..")))
STM32_KCONFIG_DIR = os.path.join(RIOTBASE, "cpu/stm32/kconfig")
STM32_VENDOR_DIR = os.path.join(RIOTBASE, "cpu/stm32/include/vendor/cmsis")
def parse_sheet(cpu_fam, sheets):
"""Parse the Excel sheet and return a dict."""
models = []
for sheet in sheets:
# Load the content of the xlsx sheet
work_book = xlrd.open_workbook(sheet)
sheet = work_book.sheet_by_name('ProductsList')
# Extract models from sheet
for rownum in range(sheet.nrows):
row = sheet.row_values(rownum)
if not row[0].startswith("STM32"):
continue
models.append(row[0].replace("-", "_"))
return sorted(models)
def parse_cpu_lines(cpu_fam):
"""Return the list of available CPU lines."""
headers_dir = os.path.join(STM32_VENDOR_DIR, cpu_fam, "Include")
cpu_lines = [
header[:-2].upper() for header in os.listdir(headers_dir)
if (
header.startswith("stm32") and
header != "stm32{}xx.h".format(cpu_fam)
)
]
return sorted(cpu_lines)
def _match(model, line):
"""Return True if a cpu model matches a cpu line, False otherwise."""
model = model.replace("_", "")
family_model = model[6:9]
family_model_letter1 = model[9]
family_model_letter2 = model[10] if len(model) >= 11 else None
family_model_letter3 = model[11] if len(model) == 12 else None
family_line = line[6:9]
family_line_letter1 = line[9]
family_line_letter2 = line[10] if len(line) >= 11 else None
family_line_letter3 = line[11] if len(line) == 12 else None
if family_line_letter1 == "X" and family_line_letter2 == "X":
letters_match = True
elif family_line_letter1 == "X":
if family_model_letter3 is not None or family_line_letter3 is not None:
letters_match = (
(family_line_letter2 == family_model_letter2) and
(family_line_letter3 == family_model_letter3)
)
else:
letters_match = family_line_letter2 == family_model_letter2
elif family_line_letter2 == "X":
letters_match = family_line_letter1 == family_model_letter1
else:
letters_match = False
return family_model == family_line and letters_match
def get_context(cpu_fam, models, lines):
"""Return a dict where keys are the models and values are the lines/fam."""
mapping = []
for model in models:
found_line = False
for line in lines:
if _match(model, line):
mapping.append(
{"model": model, "line": "CPU_LINE_{}".format(line)}
)
found_line = True
# if a model has no matching line, just match it to the upper cpu
# fam level
if not found_line:
mapping.append(
{"model": model, "line": "CPU_FAM_{}".format(cpu_fam)}
)
return {"models": mapping, "lines": lines, "fam": cpu_fam}
def generate_kconfig(kconfig, context, overwrite, verbose):
"""Generic kconfig file generator."""
loader = FileSystemLoader(searchpath=CURRENT_DIR)
env = Environment(
loader=loader, trim_blocks=False, lstrip_blocks=True,
keep_trailing_newline=True
)
template_file = os.path.join("Kconfig.{}.j2".format(kconfig))
env.globals.update(zip=zip)
template = env.get_template(template_file)
render = template.render(**context)
kconfig_file = os.path.join(
STM32_KCONFIG_DIR, context["fam"], "Kconfig.{}".format(kconfig)
)
if (not os.path.exists(kconfig_file) or
(os.path.exists(kconfig_file) and
overwrite is True and
kconfig == "models")):
with open(kconfig_file, "w") as f_dest:
f_dest.write(render)
if verbose:
print("{}:".format(os.path.basename(kconfig_file)))
print("-" * (len(os.path.basename(kconfig_file)) + 1) + "\n")
print(render)
def main(args):
"""Main function."""
models = parse_sheet(args.cpu_fam, args.sheets)
lines = parse_cpu_lines(args.cpu_fam)
context = get_context(args.cpu_fam, models, lines)
if args.verbose:
print("Generated kconfig files:\n")
for kconfig in ["lines", "models"]:
generate_kconfig(kconfig, context, args.overwrite, args.verbose)
PARSER = argparse.ArgumentParser()
PARSER.add_argument("cpu_fam",
help="STM32 CPU Family")
PARSER.add_argument("--sheets", "-s", nargs='+',
help="Excel sheet containing the list of products")
PARSER.add_argument("--overwrite", "-o", action="store_true",
help="Overwrite any existing Kconfig file")
PARSER.add_argument("--verbose", "-v", action="store_true",
help="Print generated file content")
if __name__ == "__main__":
main(PARSER.parse_args())

View File

@ -0,0 +1,2 @@
xlrd
jinja2