summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Hatle <mark.hatle@amd.com>2025-06-17 18:39:38 -0500
committerRichard Purdie <richard.purdie@linuxfoundation.org>2025-06-20 09:52:28 +0100
commit3c5c4cfa6bf4aa88bcd69e2688fdceea6a655165 (patch)
tree06abd25119dcff90922795d0cc7f9c2a31e7af30
parent63fb85ec3709ae6899ad82ae4cc6ce00e11e0018 (diff)
downloadpoky-3c5c4cfa6bf4aa88bcd69e2688fdceea6a655165.tar.gz
riscv tunes: ISA Implementation of RISC-V tune features
This implements the following base ISAs: * rv32i, rv64i * rv32e, rv64i The following ABIs: * ilp32, ilp32e, ilp32f, ilp32d * lp64, lp64e, lp64f, lp64d The following ISA extension are also implemented: * M - Integer Multiplication and Division Extension * A - Atomic Memory Extension * F - Single-Precision Floating-Point Extension * D - Double-Precision Floating-Point Extension * C - Compressed Extension * B - Bit Manipulation Extension (implies Zba, Zbb, Zbs) * V - Vector Operations Extension * Zicsr - Control and Status Register Access Extension * Zifencei - Instruction-Fetch Fence Extension * Zba - Address bit manipulation extension * Zbb - Basic bit manipulation extension * Zbc - Carry-less multiplication extension * Zbs - Single-bit manipulation extension * Zicbom - Cache-block management extension The existing processors tunes are preserved: * riscv64 (rv64gc) * riscv32 (rv32gc) * riscv64nf (rv64imac_zicsr_zifencei) * riscv32nf (rv32imac_zicsr_zifencei) * riscv64nc (rv64imafd_zicsr_zifencei) Previously defined feature 'big-endian' has been removed as it was not used. (From OE-Core rev: bcaf298a146dfd10e4c8f44223ea083bc4baf45c) Signed-off-by: Mark Hatle <mark.hatle@amd.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--meta/conf/machine/include/riscv/README122
-rw-r--r--meta/conf/machine/include/riscv/arch-riscv.inc138
-rw-r--r--meta/conf/machine/include/riscv/tune-riscv.inc40
-rw-r--r--meta/conf/machine/qemuriscv32.conf4
-rw-r--r--meta/lib/oe/__init__.py2
-rw-r--r--meta/lib/oe/tune.py81
6 files changed, 349 insertions, 38 deletions
diff --git a/meta/conf/machine/include/riscv/README b/meta/conf/machine/include/riscv/README
new file mode 100644
index 0000000000..beef68f523
--- /dev/null
+++ b/meta/conf/machine/include/riscv/README
@@ -0,0 +1,122 @@
12025/06/08 - Mark Hatle <mark.hatle@amd.com>
2 - Initial Revision
3
4The RISC-V ISA is broken into two parts, a base ISA and extensions. As
5of the writing of this document these are documented at:
6
7https://lf-riscv.atlassian.net/wiki/spaces/HOME/pages/16154769/RISC-V+Technical+Specifications
8
9Specifically "The RISC-V Instruction Set Manual Volume I: Unprivileged ISA"
10was used to create this implementation.
11
12Requirements
13------------
14As RISC-V is a “variable” ISA (a base isa plus numerous extensions), a
15mechanism is required to specify a series of ISA features that a user or
16tune can use to specify a specific CPU instantiation.
17
18Not all ratified or draft features should or can be implemented with the
19available resources.
20
21The implementation should work for Linux, baremetal (newlib), zephyr and
22other operating systems. Supported extensions should be based on
23real-world examples.
24
25Linux
26-----
27Linux required base and support extensions should be available. Linux
28requires:
29* Base: rv32ima & rv64ima
30* Optional FPU: fd
31* Optional RISCV_ISA_C: c
32* Optiona RISCV_ISA_V: v
33* Required additional: _zicsr_zifencei
34* Optional RISCV_ISA_ZBA: _zba
35* Optional RISCV_ISA_ZBB: _zbb
36* Optional RISCV_ISA_ZBC: _zbc (not supported by current QEMU design)
37
38See: https://git.yoctoproject.org/linux-yocto/tree/arch/riscv/Makefile?h=v6.12/base
39
40Baremetal
41---------
42AMD Microblaze-V FPGA support uses the following static configurations:
43Base: rv32e, rv32i, rv64i
44Extensions: m, a, f, d, c, b, zicsr, zifencei
45
46Zephyr
47------
48AMD Microblaze-V development for Zephyr is the same as Baremetal, with a
49few additional extensions: zbc, zicbom
50
51ABI
52---
53The following ABIs are supported GNU tools and some combination of systems.
54* ilp32 - Integer, long and pointer are 32-bit
55* lp64 - Long and pointer are 64-bit (integer is 32-bit)
56
57The ABI is dependent upon the core system implementation, as ilp32 can
58only used on an ‘rv32’ system, while lp64 can only be used on an ‘rv64’
59system.
60
61There are additional variations of each ABI:
62* e - used with the Reduced register extension
63* f - used when single precision floating point (but not double precision) is
64 enabled
65* d - used when both single and double precision floating point is enabled
66
67Based on the above, the ABI should be automatically determined based on
68the selected Base ISA and Extensions.
69
70Implementation
71--------------
72To make it easier to generate the RISC-V canonical arch, ISA based -march,
73and the ABI string, a few new variables are added for specific RISC-V items.
74
75TUNE_RISCV_ARCH - This contains the canonical GNU style arch, generally this
76 will evaluate to "riscv32" or "riscv64".
77
78TUNE_RISCV_MARCH - This will contain an ISA based -march string compatible
79 with gcc and similar toolchains. For example:
80 rv32imacfd_zicsr_zifencei
81
82TUNE_RISCV_ABI - This is the generated ABI that corresponds to the ARCH and
83 MARCH/ISA values. For riscv32, the value will be ilp32
84 (int, long and pointer is 32-bit) with the ISA
85 variation. For riscv64, the value will be lp64 (long
86 and pointer are 64-bit bit, while int is 32-bit) with the
87 ISA variation. The ISA affects the ABI when the 'e', 'f'
88 and 'd' extension are used.
89
90TUNE_RISCV_PKGARCH - This is the generated PKGARCH value.
91
92The standard variables are defined as:
93
94TUNE_CCARGS = "${@ '-march=${TUNE_RISCV_MARCH} -mabi=${TUNE_RISCV_ABI}' if not d.getVar('TUNE_CCARGS:tune-${DEFAULTTUNE}') else 'TUNE_CCARGS:tune-${DEFAULTTUNE}'}"
95
96The above will allow the user to specify an implementation specific
97TUNE_CCARGS for a given processor tune if the default implementtion is
98not adequate for some reason. It is expected that most, if not all,
99implementations will use the default behavior.
100
101TUNE_ARCH = "${TUNE_RISCV_ARCH}"
102TUNE_PKGARCH = "${TUNE_RISCV_PKGARCH}"
103
104The above two will always base their setting off the standard TUNE_FEATURES.
105
106Ratified and draft extensions should be implemented as TUNE_FEATURES in
107the arch-riscv.inc file.
108
109Vendor specific extensions and processor specific settings should go
110into a 'tune-<vendor>.inc' file, with tune-riscv.inc being reserved for
111general purpose tunes.
112
113TUNE_FEATURE Helper
114-------------------
115A special helper function has been written that will convert RISC-V ISA
116notation into TUNE_FEATURE notion, for example:
117
118rv32g -> rv 32 i m a f d zicsr zifencei
119
120The helper can be called using oe.tune.riscv_isa_to_tune("<ISA>") such as
121oe.tune.riscv_isa_to_tune("rv64gc") which would return:
122 rv 64 i m a f d c zicsr zifencei
diff --git a/meta/conf/machine/include/riscv/arch-riscv.inc b/meta/conf/machine/include/riscv/arch-riscv.inc
index b34064e78f..99bed8fde5 100644
--- a/meta/conf/machine/include/riscv/arch-riscv.inc
+++ b/meta/conf/machine/include/riscv/arch-riscv.inc
@@ -1,14 +1,140 @@
1# RISCV Architecture definition 1# RISCV Architecture definition
2 2
3DEFAULTTUNE ?= "riscv64" 3# Based on the RISC-V Instruction Set Manual Volume I: Unprivileged ISA from May 2025
4# As well as the RISC-V options for using GCC (as of June 2025)
4 5
5TUNE_ARCH = "${TUNE_ARCH:tune-${DEFAULTTUNE}}" 6# Note: the following should be implemented in the order that GCC expects
6TUNE_PKGARCH = "${TUNE_PKGARCH:tune-${DEFAULTTUNE}}" 7# -march= values to be defined in.
7TUNE_CCARGS:append = "${@bb.utils.contains('TUNE_FEATURES', 'riscv64nf', ' -mabi=lp64', ' ', d)}"
8TUNE_CCARGS:append = "${@bb.utils.contains('TUNE_FEATURES', 'riscv32nf', ' -mabi=ilp32', ' ', d)}"
9 8
10TUNE_CCARGS:append = "${@bb.utils.contains('TUNE_FEATURES', 'riscv64nc', ' -march=rv64imafd', ' ', d)}" 9# Base ISA
10# All supported march strings must start with rv32 or rv64
11TUNEVALID[rv] = "RISC-V"
12TUNE_RISCV_ARCH = "${@bb.utils.contains("TUNE_FEATURES", "rv", "riscv", "", d)}"
13TUNE_RISCV_MARCH = "${@bb.utils.contains("TUNE_FEATURES", "rv", "rv", "", d)}"
14TUNE_RISCV_ABI = ""
11 15
16# There are two primary ABIs, ilp32 and lp64
17# There are variants of both, that appears to be based on extensions above
18# For example:
19# rv32i uses ilp32, rv32e uses ilp32e, rv32f uses ilp32f
20# rv64i uses lp64, rv64if uses lp64f, rv64id uses lp64d
21TUNEVALID[32] = "ISA XLEN - 32-bit"
22TUNECONFLICTS[32] = "64"
23TUNE_RISCV_ARCH .= "${@bb.utils.contains("TUNE_FEATURES", "32", "32", "", d)}"
24TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "32", "32", "", d)}"
25TUNE_RISCV_ABI .= "${@bb.utils.contains("TUNE_FEATURES", "32", "ilp32", "", d)}"
26
27TUNEVALID[64] = "ISA XLEN - 64-bit"
28TUNECONFLICTS[64] = "32"
29TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "64", "64", "", d)}"
30TUNE_RISCV_ARCH .= "${@bb.utils.contains("TUNE_FEATURES", "64", "64", "", d)}"
31TUNE_RISCV_ABI .= "${@bb.utils.contains("TUNE_FEATURES", "64", "lp64", "", d)}"
32
33# The package arch starts with the canonical arch, but adds some extensions to make
34# package compatibility clear
35TUNE_RISCV_PKGARCH = "${TUNE_RISCV_ARCH}"
36
37# i, e, or g are defined by gcc, but 'g' refers to 'i' + extensions 'MAFD Zicsr Zifencei'
38# So 'g' will not be defined here as it is an abbreviation of the expanded version
39TUNEVALID[e] = "Reduced register base integer extension"
40TUNECONFLICTS[e] = "i"
41TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "e", "e", "", d)}"
42TUNE_RISCV_ABI .= "${@bb.utils.contains("TUNE_FEATURES", "e", "e", "", d)}"
43TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "e", "e", "", d)}"
44
45TUNEVALID[i] = "Base integer extension"
46TUNECONFLICTS[i] = "e"
47TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "i", "i", "", d)}"
48TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "i", "i", "", d)}"
49
50# Extensions
51TUNEVALID[m] = "Integer multiplication and division extension"
52TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "m", "m", "", d)}"
53TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "m", "m", "", d)}"
54
55TUNEVALID[a] = "Atomic extension"
56TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "a", "a", "", d)}"
57TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "a", "a", "", d)}"
58
59TUNEVALID[f] = "Single-precision floating-point extension"
60TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "f d", "f", "", d)}"
61TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "f d", "f", "", d)}"
62
63TUNEVALID[d] = "Double-precision floating-point extension"
64TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "d", "d", "", d)}"
65TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "d", "d", "", d)}"
66
67# Only f OR d, but just one
68TUNE_RISCV_ABI .= "${@bb.utils.contains("TUNE_FEATURES", "d", "d", bb.utils.contains("TUNE_FEATURES", "f", "f", "", d), d)}"
69
70TUNEVALID[c] = "Compressed extension"
71TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "c", "c", "", d)}"
72TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "c", "c", "", d)}"
73
74TUNEVALID[b] = "Bit Manipulation extension"
75# Handled below via zba, zbb, zbs
76# This matches current Linux kernel behavior
77#TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "b", "b", "", d)}"
78#TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "b", "b", "", d)}"
79
80TUNEVALID[v] = "Vector operations extension"
81TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "v", "v", "", d)}"
82TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "v", "v", "", d)}"
83
84# Now the special Z extensions
85TUNEVALID[zicbom] = "Cache-block management extension"
86TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zicbom", "_zicbom", "", d)}"
87TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zicbom", "_zicbom", "", d)}"
88
89TUNEVALID[zicsr] = "Control and status register access extension"
90TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zicsr f d", "_zicsr", "", d)}"
91# If zicsr (or zifencei) is in the path, OpenSBI fails to use the extensions, do to (Makefile):
92# # Check whether the assembler and the compiler support the Zicsr and Zifencei extensions
93# CC_SUPPORT_ZICSR_ZIFENCEI := $(shell $(CC) $(CLANG_TARGET) $(RELAX_FLAG) -nostdlib -march=rv$(OPENSBI_CC_XLEN)imafd_zicsr_zifencei -x c /dev/null -o /dev/null 2>&1 | grep -e "zicsr" -e "zifencei" > /dev/null && echo n || echo y)
94# this will match on the path containing zicsr or zifencei when an error is reported, which
95# will always happens in this check.
96#
97# Yocto Project Bugzilla 15897
98#
99#TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zicsr f d", "_zicsr", "", d)}"
100
101TUNEVALID[zifencei] = "Instruction-fetch fence extension"
102TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "zifencei", "_zifencei", "", d)}"
103# See above Bug 15897
104#TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "zifencei", "_zifencei", "", d)}"
105
106TUNEVALID[zba] = "Address bit manipulation extension"
107TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zba", "_zba", "", d)}"
108TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zba", "_zba", "", d)}"
109
110TUNEVALID[zbb] = "Basic bit manipulation extension"
111TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zbb", "_zbb", "", d)}"
112TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zbb", "_zbb", "", d)}"
113
114TUNEVALID[zbc] = "Carry-less multiplication extension"
115TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zbc", "_zbc", "", d)}"
116TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zbc", "_zbc", "", d)}"
117
118TUNEVALID[zbs] = "Single-bit manipulation extension"
119TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zbs", "_zbs", "", d)}"
120TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zbs", "_zbs", "", d)}"
121
122# Construct TUNE_CCARGS
123# This should result in a CCARG similar to:
124# -march=rv32imac -mabi=ilp32
125TUNE_CCARGS = "${@ '-march=${TUNE_RISCV_MARCH} -mabi=${TUNE_RISCV_ABI}' if not d.getVar('TUNE_CCARGS:tune-${DEFAULTTUNE}') else 'TUNE_CCARGS:tune-${DEFAULTTUNE}'}"
126
127# Construct TUNE_ARCH
128# This should result in an arch string similar to:
129# riscv32
130TUNE_ARCH = "${TUNE_RISCV_ARCH}"
131
132# Construct TUNE_PKGARCH
133# This should result in a package are like:
134# riscv32imac
135TUNE_PKGARCH = "${TUNE_RISCV_PKGARCH}"
136
137# Misc settings
12# Fix: ld: unrecognized option '--hash-style=sysv' 138# Fix: ld: unrecognized option '--hash-style=sysv'
13LINKER_HASH_STYLE:libc-newlib = "" 139LINKER_HASH_STYLE:libc-newlib = ""
14LINKER_HASH_STYLE:libc-picolibc = "" 140LINKER_HASH_STYLE:libc-picolibc = ""
diff --git a/meta/conf/machine/include/riscv/tune-riscv.inc b/meta/conf/machine/include/riscv/tune-riscv.inc
index 804712077e..12c1125c8b 100644
--- a/meta/conf/machine/include/riscv/tune-riscv.inc
+++ b/meta/conf/machine/include/riscv/tune-riscv.inc
@@ -1,41 +1,23 @@
1require conf/machine/include/riscv/arch-riscv.inc 1require conf/machine/include/riscv/arch-riscv.inc
2 2
3TUNEVALID[riscv64] = "Enable 64-bit RISC-V optimizations" 3DEFAULTTUNE ?= "riscv64"
4TUNEVALID[riscv32] = "Enable 32-bit RISC-V optimizations"
5
6TUNEVALID[riscv64nf] = "Enable 64-bit RISC-V optimizations no floating point"
7TUNEVALID[riscv32nf] = "Enable 32-bit RISC-V optimizations no floating point"
8
9TUNEVALID[riscv64nc] = "Enable 64-bit RISC-V optimizations without compressed instructions"
10
11TUNEVALID[bigendian] = "Big endian mode"
12 4
13AVAILTUNES += "riscv64 riscv32 riscv64nc riscv64nf riscv32nf" 5AVAILTUNES += "riscv64 riscv32 riscv64nc riscv64nf riscv32nf"
14 6
15# Default 7# Default
16TUNE_FEATURES:tune-riscv64 = "riscv64" 8TUNE_FEATURES:tune-riscv64 := "${@oe.tune.riscv_isa_to_tune("rv64gc")}"
17TUNE_ARCH:tune-riscv64 = "riscv64" 9PACKAGE_EXTRA_ARCHS:tune-riscv64 = "${TUNE_RISCV_PKGARCH}"
18TUNE_PKGARCH:tune-riscv64 = "riscv64"
19PACKAGE_EXTRA_ARCHS:tune-riscv64 = "riscv64"
20 10
21TUNE_FEATURES:tune-riscv32 = "riscv32" 11TUNE_FEATURES:tune-riscv32 := "${@oe.tune.riscv_isa_to_tune("rv32gc")}"
22TUNE_ARCH:tune-riscv32 = "riscv32" 12PACKAGE_EXTRA_ARCHS:tune-riscv32 = "${TUNE_RISCV_PKGARCH}"
23TUNE_PKGARCH:tune-riscv32 = "riscv32"
24PACKAGE_EXTRA_ARCHS:tune-riscv32 = "riscv32"
25 13
26# No float 14# No float
27TUNE_FEATURES:tune-riscv64nf = "${TUNE_FEATURES:tune-riscv64} riscv64nf" 15TUNE_FEATURES:tune-riscv64nf := "${@oe.tune.riscv_isa_to_tune("rv64imac_zicsr_zifencei")}"
28TUNE_ARCH:tune-riscv64nf = "riscv64" 16PACKAGE_EXTRA_ARCHS:tune-riscv64nf = "${TUNE_RISCV_PKGARCH}"
29TUNE_PKGARCH:tune-riscv64nf = "riscv64nf"
30PACKAGE_EXTRA_ARCHS:tune-riscv64nf = "riscv64nf"
31 17
32TUNE_FEATURES:tune-riscv32nf = "${TUNE_FEATURES:tune-riscv32} riscv32nf" 18TUNE_FEATURES:tune-riscv32nf := "${@oe.tune.riscv_isa_to_tune("rv32imac_zicsr_zifencei")}"
33TUNE_ARCH:tune-riscv32nf = "riscv32" 19PACKAGE_EXTRA_ARCHS:tune-riscv32nf = "${TUNE_RISCV_PKGARCH}"
34TUNE_PKGARCH:tune-riscv32nf = "riscv32nf"
35PACKAGE_EXTRA_ARCHS:tune-riscv32nf = "riscv32nf"
36 20
37# no compressed 21# no compressed
38TUNE_FEATURES:tune-riscv64nc = "${TUNE_FEATURES:tune-riscv64} riscv64nc" 22TUNE_FEATURES:tune-riscv64nc := "${@oe.tune.riscv_isa_to_tune("rv64imafd_zicsr_zifencei")}"
39TUNE_ARCH:tune-riscv64nc = "riscv64" 23PACKAGE_EXTRA_ARCHS:tune-riscv64nc = "${TUNE_RISCV_PKGARCH}"
40TUNE_PKGARCH:tune-riscv64nc = "riscv64nc"
41PACKAGE_EXTRA_ARCHS:tune-riscv64nc = "riscv64nc"
diff --git a/meta/conf/machine/qemuriscv32.conf b/meta/conf/machine/qemuriscv32.conf
index d3858dc051..aff36c28a5 100644
--- a/meta/conf/machine/qemuriscv32.conf
+++ b/meta/conf/machine/qemuriscv32.conf
@@ -2,9 +2,9 @@
2#@NAME: generic riscv32 machine 2#@NAME: generic riscv32 machine
3#@DESCRIPTION: Machine configuration for running a generic riscv32 3#@DESCRIPTION: Machine configuration for running a generic riscv32
4 4
5require conf/machine/include/riscv/qemuriscv.inc 5DEFAULTTUNE ?= "riscv32"
6 6
7DEFAULTTUNE = "riscv32" 7require conf/machine/include/riscv/qemuriscv.inc
8 8
9PREFERRED_VERSION_openocd-native = "riscv" 9PREFERRED_VERSION_openocd-native = "riscv"
10PREFERRED_VERSION_openocd = "riscv" 10PREFERRED_VERSION_openocd = "riscv"
diff --git a/meta/lib/oe/__init__.py b/meta/lib/oe/__init__.py
index dd094a874a..73de774266 100644
--- a/meta/lib/oe/__init__.py
+++ b/meta/lib/oe/__init__.py
@@ -12,4 +12,4 @@ __path__ = extend_path(__path__, __name__)
12BBIMPORTS = ["qa", "data", "path", "utils", "types", "package", "packagedata", \ 12BBIMPORTS = ["qa", "data", "path", "utils", "types", "package", "packagedata", \
13 "packagegroup", "sstatesig", "lsb", "cachedpath", "license", "qemu", \ 13 "packagegroup", "sstatesig", "lsb", "cachedpath", "license", "qemu", \
14 "reproducible", "rust", "buildcfg", "go", "spdx30_tasks", "spdx_common", \ 14 "reproducible", "rust", "buildcfg", "go", "spdx30_tasks", "spdx_common", \
15 "cve_check"] 15 "cve_check", "tune"]
diff --git a/meta/lib/oe/tune.py b/meta/lib/oe/tune.py
new file mode 100644
index 0000000000..7fda19430d
--- /dev/null
+++ b/meta/lib/oe/tune.py
@@ -0,0 +1,81 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7# riscv_isa_to_tune(isa)
8#
9# Automatically translate a RISC-V ISA string to TUNE_FEATURES
10#
11# Abbreviations, such as rv32g -> rv32imaffd_zicsr_zifencei are supported.
12#
13# Profiles, such as rva22u64, are NOT supported, you must use ISA strings.
14#
15def riscv_isa_to_tune(isa):
16 _isa = isa.lower()
17
18 feature = []
19 iter = 0
20
21 # rv or riscv
22 if _isa[iter:].startswith('rv'):
23 feature.append('rv')
24 iter = iter + 2
25 elif _isa[iter:].startswith('riscv'):
26 feature.append('rv')
27 iter = iter + 5
28 else:
29 # Not a risc-v ISA!
30 return _isa
31
32 while (_isa[iter:]):
33 # Skip _ and whitespace
34 if _isa[iter] == '_' or _isa[iter].isspace():
35 iter = iter + 1
36 continue
37
38 # Length, just capture numbers here
39 if _isa[iter].isdigit():
40 iter_end = iter
41 while iter_end < len(_isa) and _isa[iter_end].isdigit():
42 iter_end = iter_end + 1
43
44 feature.append(_isa[iter:iter_end])
45 iter = iter_end
46 continue
47
48 # Typically i, e or g is next, followed by extensions.
49 # Extensions are single character, except for Z, Ss, Sh, Sm, Sv, and X
50
51 # If the extension starts with 'Z', 'S' or 'X' use the name until the next _, whitespace or end
52 if _isa[iter] in ['z', 's', 'x']:
53 ext_type = _isa[iter]
54 iter_end = iter + 1
55
56 # Multicharacter extension, these are supposed to have a _ before the next multicharacter extension
57 # See 37.4 and 37.5:
58 # 37.4: Underscores "_" may be used to separate ISA extensions...
59 # 37.5: All multi-letter extensions ... must be separated from other multi-letter extensions by an underscore...
60 # Some extensions permit only alphabetic characters, while others allow alphanumeric chartacters
61 while iter_end < len(_isa) and _isa[iter_end] != "_" and not _isa[iter_end].isspace():
62 iter_end = iter_end + 1
63
64 feature.append(_isa[iter:iter_end])
65 iter = iter_end
66 continue
67
68 # 'g' is special, it's an abbreviation for imafd_zicsr_zifencei
69 # When expanding the abbreviation, any additional letters must appear before the _z* extensions
70 if _isa[iter] == 'g':
71 _isa = 'imafd' + _isa[iter+1:] + '_zicsr_zifencei'
72 iter = 0
73 continue
74
75 feature.append(_isa[iter])
76 iter = iter + 1
77 continue
78
79 # Eliminate duplicates, but preserve the order
80 feature = list(dict.fromkeys(feature))
81 return ' '.join(feature)