From 06c3361a1512e1558aecbf5b68a3f0b970fb948e Mon Sep 17 00:00:00 2001 From: Alexandre Abadie Date: Thu, 1 Oct 2020 14:09:36 +0200 Subject: [PATCH] cpu/stm32/dist: add generator for kconfig cpu lines and models --- cpu/stm32/dist/kconfig/Kconfig.lines.j2 | 17 +++ cpu/stm32/dist/kconfig/Kconfig.models.j2 | 23 ++++ cpu/stm32/dist/kconfig/README.md | 78 ++++++++++++ cpu/stm32/dist/kconfig/gen_kconfig.py | 154 +++++++++++++++++++++++ cpu/stm32/dist/kconfig/requirements.txt | 2 + 5 files changed, 274 insertions(+) create mode 100644 cpu/stm32/dist/kconfig/Kconfig.lines.j2 create mode 100644 cpu/stm32/dist/kconfig/Kconfig.models.j2 create mode 100644 cpu/stm32/dist/kconfig/README.md create mode 100755 cpu/stm32/dist/kconfig/gen_kconfig.py create mode 100644 cpu/stm32/dist/kconfig/requirements.txt diff --git a/cpu/stm32/dist/kconfig/Kconfig.lines.j2 b/cpu/stm32/dist/kconfig/Kconfig.lines.j2 new file mode 100644 index 0000000000..faf09d5208 --- /dev/null +++ b/cpu/stm32/dist/kconfig/Kconfig.lines.j2 @@ -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 -%} diff --git a/cpu/stm32/dist/kconfig/Kconfig.models.j2 b/cpu/stm32/dist/kconfig/Kconfig.models.j2 new file mode 100644 index 0000000000..321f655bc4 --- /dev/null +++ b/cpu/stm32/dist/kconfig/Kconfig.models.j2 @@ -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 %} diff --git a/cpu/stm32/dist/kconfig/README.md b/cpu/stm32/dist/kconfig/README.md new file mode 100644 index 0000000000..b5d9282368 --- /dev/null +++ b/cpu/stm32/dist/kconfig/README.md @@ -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//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= make -C cpu/stm32/include/vendor +``` + +`` 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 --sheets /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 /L4ProductsList.xlsx /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 +``` diff --git a/cpu/stm32/dist/kconfig/gen_kconfig.py b/cpu/stm32/dist/kconfig/gen_kconfig.py new file mode 100755 index 0000000000..6fa117483b --- /dev/null +++ b/cpu/stm32/dist/kconfig/gen_kconfig.py @@ -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()) diff --git a/cpu/stm32/dist/kconfig/requirements.txt b/cpu/stm32/dist/kconfig/requirements.txt new file mode 100644 index 0000000000..35ec90e4f7 --- /dev/null +++ b/cpu/stm32/dist/kconfig/requirements.txt @@ -0,0 +1,2 @@ +xlrd +jinja2