scripts: introduce list_hardware.py for listing of architectures and SoCs

The list_hardware.py script parses archs.yml in all <arch-root>/arch
folders and soc.yml in all <soc-root>/soc sub-folders.

The archs.yml and soc.yml are introduced with hw model v2.

Hw model v2 removes the need for architecture knowledge of the SoCs,
and as part of this makes multi-arch and multi-core SoCs possible.

Hw model v2 also allows for greater flexibility in arch and SoC
organization as they can be organized freely.

As example SoCs can be organized by vendors, architecture, or any other
way as the socs.yml contains the path to the location of the SoC,
instead of relying on a specific arch.

Signed-off-by: Torsten Rasmussen <Torsten.Rasmussen@nordicsemi.no>
This commit is contained in:
Torsten Rasmussen
2023-11-02 22:19:32 +01:00
committed by Jamie McCrae
parent a4d1980c35
commit 61bbfb5ba2
6 changed files with 497 additions and 0 deletions

4
arch/archs.yml Normal file
View File

@@ -0,0 +1,4 @@
archs:
- name: empty
folder: empty
comment: initial file, no arch v2 defined.

108
cmake/modules/hwm_v2.cmake Normal file
View File

@@ -0,0 +1,108 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (c) 2023, Nordic Semiconductor ASA
# This CMake module works together with the list_hardware.py script to obtain
# all archs and SoC implementations defined in the Zephyr build system.
#
# The result from list_hardware.py is then used to generate Kconfig files for
# the build system.
#
# The following files are generated in '<kconfig-binary-dir>/soc'
# - Kconfig.defconfig: Contains references to SoC defconfig files for Zephyr integration.
# - Kconfig: Contains references to regular SoC Kconfig files for Zephyr integration.
# - Kconfig.soc: Contains references to generic SoC Kconfig files.
#
# The following file is generated in '<kconfig-binary-dir>/arch'
# - Kconfig: Contains references to regular arch Kconfig files for Zephyr integration.
include_guard(GLOBAL)
if(NOT HWMv2)
return()
endif()
# Internal helper function for creation of Kconfig files.
function(kconfig_gen bin_dir file dirs)
file(MAKE_DIRECTORY "${bin_dir}")
set(kconfig_file ${bin_dir}/${file})
foreach(dir ${dirs})
file(APPEND ${kconfig_file} "osource \"${dir}/${file}\"\n")
endforeach()
endfunction()
# 'SOC_ROOT' and 'ARCH_ROOT' are prioritized lists of directories where their
# implementations may be found. It always includes ${ZEPHYR_BASE}/[arch|soc]
# at the lowest priority.
list(APPEND SOC_ROOT ${ZEPHYR_BASE})
list(APPEND ARCH_ROOT ${ZEPHYR_BASE})
list(TRANSFORM ARCH_ROOT PREPEND "--arch-root=" OUTPUT_VARIABLE arch_root_args)
list(TRANSFORM SOC_ROOT PREPEND "--soc-root=" OUTPUT_VARIABLE soc_root_args)
execute_process(COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/list_hardware.py
${arch_root_args} ${soc_root_args}
--archs --socs
--cmakeformat={TYPE}\;{NAME}\;{DIR}\;{HWM}
OUTPUT_VARIABLE ret_hw
ERROR_VARIABLE err_hw
RESULT_VARIABLE ret_val
)
if(ret_val)
message(FATAL_ERROR "Error listing hardware.\nError message: ${err_hw}")
endif()
set(kconfig_soc_source_dir)
while(TRUE)
string(FIND "${ret_hw}" "\n" idx REVERSE)
math(EXPR start "${idx} + 1")
string(SUBSTRING "${ret_hw}" ${start} -1 line)
string(SUBSTRING "${ret_hw}" 0 ${idx} ret_hw)
cmake_parse_arguments(HWM "" "TYPE" "" ${line})
if(HWM_TYPE STREQUAL "arch")
cmake_parse_arguments(ARCH_V2 "" "NAME;DIR" "" ${line})
list(APPEND kconfig_arch_source_dir "${ARCH_V2_DIR}")
list(APPEND ARCH_V2_NAME_LIST ${ARCH_V2_NAME})
string(TOUPPER "${ARCH_V2_NAME}" ARCH_V2_NAME_UPPER)
set(ARCH_V2_${ARCH_V2_NAME_UPPER}_DIR ${ARCH_V2_DIR})
elseif(HWM_TYPE MATCHES "^soc|^series|^family")
cmake_parse_arguments(SOC_V2 "" "NAME;DIR;HWM" "" ${line})
list(APPEND kconfig_soc_source_dir "${SOC_V2_DIR}")
if(HWM_TYPE STREQUAL "soc")
set(setting_name SOC_${SOC_V2_NAME}_DIR)
else()
set(setting_name SOC_${HWM_TYPE}_${SOC_V2_NAME}_DIR)
endif()
string(TOUPPER ${setting_name} setting_name)
set(${setting_name} ${SOC_V2_DIR})
endif()
if(idx EQUAL -1)
break()
endif()
endwhile()
list(REMOVE_DUPLICATES kconfig_soc_source_dir)
# Support multiple ARCH_ROOT and SOC_ROOT
set(arch_kconfig_file Kconfig)
set(soc_defconfig_file Kconfig.defconfig)
set(soc_zephyr_file Kconfig)
set(soc_kconfig_file Kconfig.soc)
set(arch_kconfig_header "# Load arch Kconfig descriptions.\n")
set(defconfig_header "# Load Zephyr SoC Kconfig defconfig.\n")
set(soc_zephyr_header "# Load Zephyr SoC Kconfig descriptions.\n")
set(soc_kconfig_header "# Load SoC Kconfig descriptions.\n")
file(WRITE ${KCONFIG_BINARY_DIR}/arch/${arch_kconfig_file} "${arch_kconfig_header}")
file(WRITE ${KCONFIG_BINARY_DIR}/soc/${soc_defconfig_file} "${defconfig_header}")
file(WRITE ${KCONFIG_BINARY_DIR}/soc/${soc_zephyr_file} "${soc_zephyr_header}")
file(WRITE ${KCONFIG_BINARY_DIR}/soc/${soc_kconfig_file} "${soc_kconfig_header}")
kconfig_gen("${KCONFIG_BINARY_DIR}/arch" "${arch_kconfig_file}" "${kconfig_arch_source_dir}")
kconfig_gen("${KCONFIG_BINARY_DIR}/soc" "${soc_defconfig_file}" "${kconfig_soc_source_dir}")
kconfig_gen("${KCONFIG_BINARY_DIR}/soc" "${soc_zephyr_file}" "${kconfig_soc_source_dir}")
kconfig_gen("${KCONFIG_BINARY_DIR}/soc" "${soc_kconfig_file}" "${kconfig_soc_source_dir}")

View File

@@ -96,6 +96,7 @@ list(APPEND zephyr_cmake_modules zephyr_module)
list(APPEND zephyr_cmake_modules boards)
list(APPEND zephyr_cmake_modules shields)
list(APPEND zephyr_cmake_modules snippets)
list(APPEND zephyr_cmake_modules hwm_v2)
list(APPEND zephyr_cmake_modules arch)
list(APPEND zephyr_cmake_modules configuration_files)
list(APPEND zephyr_cmake_modules generated_file_directories)

283
scripts/list_hardware.py Executable file
View File

@@ -0,0 +1,283 @@
#!/usr/bin/env python3
# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
import argparse
from dataclasses import dataclass
from pathlib import Path, PurePath
import pykwalify.core
import sys
from typing import List
import yaml
SOC_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'soc-schema.yml')
with open(SOC_SCHEMA_PATH, 'r') as f:
soc_schema = yaml.safe_load(f.read())
ARCH_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'arch-schema.yml')
with open(ARCH_SCHEMA_PATH, 'r') as f:
arch_schema = yaml.safe_load(f.read())
SOC_YML = 'soc.yml'
ARCHS_YML_PATH = PurePath('arch/archs.yml')
class Systems:
def __init__(self, folder='', soc_yaml=None):
self._socs = []
self._series = []
self._families = []
if soc_yaml is None:
return
try:
data = yaml.safe_load(soc_yaml)
pykwalify.core.Core(source_data=data,
schema_data=soc_schema).validate()
except (yaml.YAMLError, pykwalify.errors.SchemaError) as e:
sys.exit(f'ERROR: Malformed yaml {soc_yaml.as_posix()}', e)
for f in data.get('family', []):
family = Family(f['name'], folder, [], [])
for s in f.get('series', []):
series = Series(s['name'], folder, f['name'], [])
socs = [(Soc(soc['name'],
[c['name'] for c in soc.get('cpuclusters', [])],
folder, s['name'], f['name']))
for soc in s.get('socs', [])]
series.socs.extend(socs)
self._series.append(series)
self._socs.extend(socs)
family.series.append(series)
family.socs.extend(socs)
socs = [(Soc(soc['name'],
[c['name'] for c in soc.get('cpuclusters', [])],
folder, None, f['name']))
for soc in f.get('socs', [])]
self._socs.extend(socs)
self._families.append(family)
for s in data.get('series', []):
series = Series(s['name'], folder, '', [])
socs = [(Soc(soc['name'],
[c['name'] for c in soc.get('cpuclusters', [])],
folder, s['name'], ''))
for soc in s.get('socs', [])]
series.socs.extend(socs)
self._series.append(series)
self._socs.extend(socs)
socs = [(Soc(soc['name'],
[c['name'] for c in soc.get('cpuclusters', [])],
folder, '', ''))
for soc in data.get('socs', [])]
self._socs.extend(socs)
@staticmethod
def from_file(socs_file):
'''Load SoCs from a soc.yml file.
'''
try:
with open(socs_file, 'r') as f:
socs_yaml = f.read()
except FileNotFoundError as e:
sys.exit(f'ERROR: socs.yml file not found: {socs_file.as_posix()}', e)
return Systems(str(socs_file.parent), socs_yaml)
@staticmethod
def from_yaml(socs_yaml):
'''Load socs from a string with YAML contents.
'''
return Systems('', socs_yaml)
def extend(self, systems):
self._families.extend(systems.get_families())
self._series.extend(systems.get_series())
self._socs.extend(systems.get_socs())
def get_families(self):
return self._families
def get_series(self):
return self._series
def get_socs(self):
return self._socs
def get_soc(self, name):
try:
return next(s for s in self._socs if s.name == name)
except StopIteration:
sys.exit(f"ERROR: SoC '{name}' is not found, please ensure that the SoC exists "
f"and that soc-root containing '{name}' has been correctly defined.")
@dataclass
class Soc:
name: str
cpuclusters: List[str]
folder: str
series: str = ''
family: str = ''
@dataclass
class Series:
name: str
folder: str
family: str
socs: List[Soc]
@dataclass
class Family:
name: str
folder: str
series: List[Series]
socs: List[Soc]
def find_v2_archs(args):
ret = {'archs': []}
for root in args.arch_roots:
archs_yml = root / ARCHS_YML_PATH
if Path(archs_yml).is_file():
with Path(archs_yml).open('r') as f:
archs = yaml.safe_load(f.read())
try:
pykwalify.core.Core(source_data=archs, schema_data=arch_schema).validate()
except pykwalify.errors.SchemaError as e:
sys.exit('ERROR: Malformed "build" section in file: {}\n{}'
.format(archs_yml.as_posix(), e))
if args.arch is not None:
archs = {'archs': list(filter(
lambda arch: arch.get('name') == args.arch, archs['archs']))}
for arch in archs['archs']:
arch.update({'path': root / 'arch' / arch['path']})
arch.update({'hwm': 'v2'})
arch.update({'type': 'arch'})
ret['archs'].extend(archs['archs'])
return ret
def find_v2_systems(args):
yml_files = []
systems = Systems()
for root in args.soc_roots:
yml_files.extend((root / 'soc').rglob(SOC_YML))
for soc_yml in yml_files:
if soc_yml.is_file():
systems.extend(Systems.from_file(soc_yml))
return systems
def parse_args():
parser = argparse.ArgumentParser(allow_abbrev=False)
add_args(parser)
return parser.parse_args()
def add_args(parser):
default_fmt = '{name}'
parser.add_argument("--soc-root", dest='soc_roots', default=[],
type=Path, action='append',
help='add a SoC root, may be given more than once')
parser.add_argument("--soc", default=None, help='lookup the specific soc')
parser.add_argument("--soc-series", default=None, help='lookup the specific soc series')
parser.add_argument("--soc-family", default=None, help='lookup the specific family')
parser.add_argument("--socs", action='store_true', help='lookup all socs')
parser.add_argument("--arch-root", dest='arch_roots', default=[],
type=Path, action='append',
help='add a arch root, may be given more than once')
parser.add_argument("--arch", default=None, help='lookup the specific arch')
parser.add_argument("--archs", action='store_true', help='lookup all archs')
parser.add_argument("--format", default=default_fmt,
help='''Format string to use to list each soc.''')
parser.add_argument("--cmakeformat", default=None,
help='''CMake format string to use to list each arch/soc.''')
def dump_v2_archs(args):
archs = find_v2_archs(args)
for arch in archs['archs']:
if args.cmakeformat is not None:
info = args.cmakeformat.format(
TYPE='TYPE;' + arch['type'],
NAME='NAME;' + arch['name'],
DIR='DIR;' + str(arch['path']),
HWM='HWM;' + arch['hwm'],
# Below is non exising for arch but is defined here to support
# common formatting string.
SERIES='',
FAMILY='',
ARCH='',
VENDOR=''
)
else:
info = args.format.format(
type=arch.get('type'),
name=arch.get('name'),
dir=arch.get('path'),
hwm=arch.get('hwm'),
# Below is non exising for arch but is defined here to support
# common formatting string.
series='',
family='',
arch='',
vendor=''
)
print(info)
def dump_v2_system(args, type, system):
if args.cmakeformat is not None:
info = args.cmakeformat.format(
TYPE='TYPE;' + type,
NAME='NAME;' + system.name,
DIR='DIR;' + system.folder,
HWM='HWM;' + 'v2'
)
else:
info = args.format.format(
type=type,
name=system.name,
dir=system.folder,
hwm='v2'
)
print(info)
def dump_v2_systems(args):
systems = find_v2_systems(args)
for f in systems.get_families():
dump_v2_system(args, 'family', f)
for s in systems.get_series():
dump_v2_system(args, 'series', s)
for s in systems.get_socs():
dump_v2_system(args, 'soc', s)
if __name__ == '__main__':
args = parse_args()
if any([args.socs, args.soc, args.soc_series, args.soc_family]):
dump_v2_systems(args)
if args.archs or args.arch is not None:
dump_v2_archs(args)

View File

@@ -0,0 +1,29 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (c) 2023, Nordic Semiconductor ASA
## A pykwalify schema for basic validation of the structure of a
## arch metadata YAML file.
##
# The archs.yml file is a simple list of key value pairs containing architectures
# and their location which is used by the build system.
type: map
mapping:
archs:
required: true
type: seq
sequence:
- type: map
mapping:
name:
required: true
type: str
desc: Name of the arch
path:
required: true
type: str
desc: Location of the arch implementation relative to the archs.yml file.
comment:
required: false
type: str
desc: Free form comment with extra information regarding the arch.

View File

@@ -0,0 +1,72 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (c) 2023, Nordic Semiconductor ASA
## A pykwalify schema for basic validation of the structure of a SoC
## metadata YAML file.
##
# The soc.yml file is a simple list of key value pairs containing SoCs
# located and the current structure level.
schema;cpucluster-schema:
required: false
type: seq
sequence:
- type: map
mapping:
name:
required: true
type: str
schema;soc-schema:
required: false
type: seq
sequence:
- type: map
mapping:
name:
required: true
type: str
cpuclusters:
include: cpucluster-schema
schema;series-schema:
required: false
type: seq
sequence:
- type: map
mapping:
name:
required: true
type: str
socs:
required: false
include: soc-schema
type: map
mapping:
family:
required: false
type: seq
sequence:
- type: map
mapping:
name:
required: true
type: str
series:
include: series-schema
socs:
include: soc-schema
series:
include: series-schema
socs:
include: soc-schema
vendor:
required: false
type: str
desc: SoC series of the SoC.
This field is of informational use and can be used for filtering of SoCs.
comment:
required: false
type: str
desc: Free form comment with extra information regarding the SoC.