diff mbox series

[1/6] riscv tunes: ISA Implementation of RISC-V tune features

Message ID 1750040965-15015-2-git-send-email-mark.hatle@kernel.crashing.org
State New
Headers show
Series ISA based RISC-V tune implementation | expand

Commit Message

Mark Hatle June 16, 2025, 2:29 a.m. UTC
From: Mark Hatle <mark.hatle@amd.com>

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.

Signed-off-by: Mark Hatle <mark.hatle@amd.com>
---
 meta/conf/machine/include/riscv/README        | 122 ++++++++++++++++
 .../conf/machine/include/riscv/arch-riscv.inc | 138 +++++++++++++++++-
 .../conf/machine/include/riscv/tune-riscv.inc |  40 ++---
 meta/conf/machine/qemuriscv32.conf            |   4 +-
 meta/lib/oe/__init__.py                       |   2 +-
 meta/lib/oe/tune.py                           |  81 ++++++++++
 6 files changed, 349 insertions(+), 38 deletions(-)
 create mode 100644 meta/conf/machine/include/riscv/README
 create mode 100644 meta/lib/oe/tune.py

Comments

Richard Purdie June 16, 2025, 10:11 a.m. UTC | #1
On Sun, 2025-06-15 at 21:29 -0500, Mark Hatle via lists.openembedded.org wrote:
> From: Mark Hatle <mark.hatle@amd.com>
> 
> 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.

I tried this (just 1/6) and it caused:

https://autobuilder.yoctoproject.org/valkyrie/#/builders/58/builds/142
https://autobuilder.yoctoproject.org/valkyrie/#/builders/45/builds/135
https://autobuilder.yoctoproject.org/valkyrie/#/builders/56/builds/114

Cheers,

Richard
Mark Hatle June 16, 2025, 2:21 p.m. UTC | #2
These all seem to be related to the same issue reported by Gyorgy Sarvari.

atomic_4 and atomic_8 functions are not working.

--Mark

On 6/16/25 5:11 AM, Richard Purdie wrote:
> On Sun, 2025-06-15 at 21:29 -0500, Mark Hatle via lists.openembedded.org wrote:
>> From: Mark Hatle <mark.hatle@amd.com>
>>
>> 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.
> 
> I tried this (just 1/6) and it caused:
> 
> https://autobuilder.yoctoproject.org/valkyrie/#/builders/58/builds/142
> https://autobuilder.yoctoproject.org/valkyrie/#/builders/45/builds/135
> https://autobuilder.yoctoproject.org/valkyrie/#/builders/56/builds/114
> 
> Cheers,
> 
> Richard
diff mbox series

Patch

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 @@ 
+2025/06/08 - Mark Hatle <mark.hatle@amd.com>
+ - Initial Revision
+
+The RISC-V ISA is broken into two parts, a base ISA and extensions.  As
+of the writing of this document these are documented at:
+
+https://lf-riscv.atlassian.net/wiki/spaces/HOME/pages/16154769/RISC-V+Technical+Specifications
+
+Specifically "The RISC-V Instruction Set Manual Volume I: Unprivileged ISA"
+was used to create this implementation.
+
+Requirements
+------------
+As RISC-V is a “variable” ISA (a base isa plus numerous extensions), a
+mechanism is required to specify a series of ISA features that a user or
+tune can use to specify a specific CPU instantiation.
+
+Not all ratified or draft features should or can be implemented with the
+available resources.
+
+The implementation should work for Linux, baremetal (newlib), zephyr and
+other operating systems.  Supported extensions should be based on
+real-world examples.
+
+Linux
+-----
+Linux required base and support extensions should be available.  Linux
+requires:
+* Base: rv32ima & rv64ima
+* Optional FPU: fd
+* Optional RISCV_ISA_C: c
+* Optiona RISCV_ISA_V: v
+* Required additional: _zicsr_zifencei
+* Optional RISCV_ISA_ZBA: _zba
+* Optional RISCV_ISA_ZBB: _zbb
+* Optional RISCV_ISA_ZBC: _zbc (not supported by current QEMU design)
+
+See: https://git.yoctoproject.org/linux-yocto/tree/arch/riscv/Makefile?h=v6.12/base
+
+Baremetal
+---------
+AMD Microblaze-V FPGA support uses the following static configurations:
+Base: rv32e, rv32i, rv64i
+Extensions: m, a, f, d, c, b, zicsr, zifencei
+
+Zephyr
+------
+AMD Microblaze-V development for Zephyr is the same as Baremetal, with a
+few additional extensions: zbc, zicbom
+
+ABI
+---
+The following ABIs are supported GNU tools and some combination of systems.
+* ilp32 - Integer, long and pointer are 32-bit
+* lp64 - Long and pointer are 64-bit (integer is 32-bit)
+
+The ABI is dependent upon the core system implementation, as ilp32 can
+only used on an ‘rv32’ system, while lp64 can only be used on an ‘rv64’
+system.
+
+There are additional variations of each ABI:
+* e - used with the Reduced register extension
+* f - used when single precision floating point (but not double precision) is
+      enabled
+* d - used when both single and double precision floating point is enabled
+
+Based on the above, the ABI should be automatically determined based on
+the selected Base ISA and Extensions.
+
+Implementation
+--------------
+To make it easier to generate the RISC-V canonical arch, ISA based -march,
+and the ABI string, a few new variables are added for specific RISC-V items.
+
+TUNE_RISCV_ARCH - This contains the canonical GNU style arch, generally this
+                  will evaluate to "riscv32" or "riscv64".
+
+TUNE_RISCV_MARCH - This will contain an ISA based -march string compatible
+                   with gcc and similar toolchains.  For example:
+                   rv32imacfd_zicsr_zifencei
+
+TUNE_RISCV_ABI - This is the generated ABI that corresponds to the ARCH and
+                 MARCH/ISA values.  For riscv32, the value will be ilp32
+                 (int, long and pointer is 32-bit) with the ISA
+                 variation.  For riscv64, the value will be lp64 (long
+                 and pointer are 64-bit bit, while int is 32-bit) with the
+                 ISA variation.  The ISA affects the ABI when the 'e', 'f'
+                 and 'd' extension are used.
+
+TUNE_RISCV_PKGARCH - This is the generated PKGARCH value.
+
+The standard variables are defined as:
+
+TUNE_CCARGS = "${@ '-march=${TUNE_RISCV_MARCH} -mabi=${TUNE_RISCV_ABI}' if not d.getVar('TUNE_CCARGS:tune-${DEFAULTTUNE}') else 'TUNE_CCARGS:tune-${DEFAULTTUNE}'}"
+
+The above will allow the user to specify an implementation specific
+TUNE_CCARGS for a given processor tune if the default implementtion is
+not adequate for some reason.  It is expected that most, if not all,
+implementations will use the default behavior.
+
+TUNE_ARCH = "${TUNE_RISCV_ARCH}"
+TUNE_PKGARCH = "${TUNE_RISCV_PKGARCH}"
+
+The above two will always base their setting off the standard TUNE_FEATURES.
+
+Ratified and draft extensions should be implemented as TUNE_FEATURES in
+the arch-riscv.inc file.
+
+Vendor specific extensions and processor specific settings should go
+into a 'tune-<vendor>.inc' file, with tune-riscv.inc being reserved for
+general purpose tunes.
+
+TUNE_FEATURE Helper
+-------------------
+A special helper function has been written that will convert RISC-V ISA
+notation into TUNE_FEATURE notion, for example:
+
+rv32g -> rv 32 i m a f d zicsr zifencei
+
+The helper can be called using oe.tune.riscv_isa_to_tune("<ISA>") such as
+oe.tune.riscv_isa_to_tune("rv64gc") which would return:
+ 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 @@ 
 # RISCV Architecture definition
 
-DEFAULTTUNE ?= "riscv64"
+# Based on the RISC-V Instruction Set Manual Volume I: Unprivileged ISA from May 2025
+# As well as the RISC-V options for using GCC (as of June 2025)
 
-TUNE_ARCH = "${TUNE_ARCH:tune-${DEFAULTTUNE}}"
-TUNE_PKGARCH = "${TUNE_PKGARCH:tune-${DEFAULTTUNE}}"
-TUNE_CCARGS:append = "${@bb.utils.contains('TUNE_FEATURES', 'riscv64nf', ' -mabi=lp64', ' ', d)}"
-TUNE_CCARGS:append = "${@bb.utils.contains('TUNE_FEATURES', 'riscv32nf', ' -mabi=ilp32', ' ', d)}"
+# Note: the following should be implemented in the order that GCC expects
+# -march= values to be defined in.
 
-TUNE_CCARGS:append = "${@bb.utils.contains('TUNE_FEATURES', 'riscv64nc', ' -march=rv64imafd', ' ', d)}"
+# Base ISA
+# All supported march strings must start with rv32 or rv64
+TUNEVALID[rv] = "RISC-V"
+TUNE_RISCV_ARCH = "${@bb.utils.contains("TUNE_FEATURES", "rv", "riscv", "", d)}"
+TUNE_RISCV_MARCH = "${@bb.utils.contains("TUNE_FEATURES", "rv", "rv", "", d)}"
+TUNE_RISCV_ABI = ""
 
+# There are two primary ABIs, ilp32 and lp64
+# There are variants of both, that appears to be based on extensions above
+# For example:
+#   rv32i uses ilp32, rv32e uses ilp32e, rv32f uses ilp32f
+#   rv64i uses lp64, rv64if uses lp64f, rv64id uses lp64d
+TUNEVALID[32] = "ISA XLEN - 32-bit"
+TUNECONFLICTS[32] = "64"
+TUNE_RISCV_ARCH .= "${@bb.utils.contains("TUNE_FEATURES", "32", "32", "", d)}"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "32", "32", "", d)}"
+TUNE_RISCV_ABI .= "${@bb.utils.contains("TUNE_FEATURES", "32", "ilp32", "", d)}"
+
+TUNEVALID[64] = "ISA XLEN - 64-bit"
+TUNECONFLICTS[64] = "32"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "64", "64", "", d)}"
+TUNE_RISCV_ARCH .= "${@bb.utils.contains("TUNE_FEATURES", "64", "64", "", d)}"
+TUNE_RISCV_ABI .= "${@bb.utils.contains("TUNE_FEATURES", "64", "lp64", "", d)}"
+
+# The package arch starts with the canonical arch, but adds some extensions to make
+# package compatibility clear
+TUNE_RISCV_PKGARCH = "${TUNE_RISCV_ARCH}"
+
+# i, e, or g are defined by gcc, but 'g' refers to 'i' + extensions 'MAFD Zicsr Zifencei'
+# So 'g' will not be defined here as it is an abbreviation of the expanded version
+TUNEVALID[e] = "Reduced register base integer extension"
+TUNECONFLICTS[e] = "i"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "e", "e", "", d)}"
+TUNE_RISCV_ABI .= "${@bb.utils.contains("TUNE_FEATURES", "e", "e", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "e", "e", "", d)}"
+
+TUNEVALID[i] = "Base integer extension"
+TUNECONFLICTS[i] = "e"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "i", "i", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "i", "i", "", d)}"
+
+# Extensions
+TUNEVALID[m] = "Integer multiplication and division extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "m", "m", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "m", "m", "", d)}"
+
+TUNEVALID[a] = "Atomic extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "a", "a", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "a", "a", "", d)}"
+
+TUNEVALID[f] = "Single-precision floating-point extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "f d", "f", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "f d", "f", "", d)}"
+
+TUNEVALID[d] = "Double-precision floating-point extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "d", "d", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "d", "d", "", d)}"
+
+# Only f OR d, but just one
+TUNE_RISCV_ABI .= "${@bb.utils.contains("TUNE_FEATURES", "d", "d", bb.utils.contains("TUNE_FEATURES", "f", "f", "", d), d)}"
+
+TUNEVALID[c] = "Compressed extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "c", "c", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "c", "c", "", d)}"
+
+TUNEVALID[b] = "Bit Manipulation extension"
+# Handled below via zba, zbb, zbs
+# This matches current Linux kernel behavior
+#TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "b", "b", "", d)}"
+#TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "b", "b", "", d)}"
+
+TUNEVALID[v] = "Vector operations extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "v", "v", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "v", "v", "", d)}"
+
+# Now the special Z extensions
+TUNEVALID[zicbom] = "Cache-block management extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zicbom", "_zicbom", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zicbom", "_zicbom", "", d)}"
+
+TUNEVALID[zicsr] = "Control and status register access extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zicsr f d", "_zicsr", "", d)}"
+# If zicsr (or zifencei) is in the path, OpenSBI fails to use the extensions, do to (Makefile):
+#   # Check whether the assembler and the compiler support the Zicsr and Zifencei extensions
+#   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)
+# this will match on the path containing zicsr or zifencei when an error is reported, which
+# will always happens in this check.
+#
+# Yocto Project Bugzilla 15897
+#
+#TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zicsr f d", "_zicsr", "", d)}"
+
+TUNEVALID[zifencei] = "Instruction-fetch fence extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains("TUNE_FEATURES", "zifencei", "_zifencei", "", d)}"
+# See above Bug 15897
+#TUNE_RISCV_PKGARCH .= "${@bb.utils.contains("TUNE_FEATURES", "zifencei", "_zifencei", "", d)}"
+
+TUNEVALID[zba] = "Address bit manipulation extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zba", "_zba", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zba", "_zba", "", d)}"
+
+TUNEVALID[zbb] = "Basic bit manipulation extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zbb", "_zbb", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zbb", "_zbb", "", d)}"
+
+TUNEVALID[zbc] = "Carry-less multiplication extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zbc", "_zbc", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "zbc", "_zbc", "", d)}"
+
+TUNEVALID[zbs] = "Single-bit manipulation extension"
+TUNE_RISCV_MARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zbs", "_zbs", "", d)}"
+TUNE_RISCV_PKGARCH .= "${@bb.utils.contains_any("TUNE_FEATURES", "b zbs", "_zbs", "", d)}"
+
+# Construct TUNE_CCARGS
+# This should result in a CCARG similar to:
+#   -march=rv32imac -mabi=ilp32
+TUNE_CCARGS = "${@ '-march=${TUNE_RISCV_MARCH} -mabi=${TUNE_RISCV_ABI}' if not d.getVar('TUNE_CCARGS:tune-${DEFAULTTUNE}') else 'TUNE_CCARGS:tune-${DEFAULTTUNE}'}"
+
+# Construct TUNE_ARCH
+# This should result in an arch string similar to:
+#   riscv32
+TUNE_ARCH = "${TUNE_RISCV_ARCH}"
+
+# Construct TUNE_PKGARCH
+# This should result in a package are like:
+#    riscv32imac
+TUNE_PKGARCH = "${TUNE_RISCV_PKGARCH}"
+
+# Misc settings
 # Fix: ld: unrecognized option '--hash-style=sysv'
 LINKER_HASH_STYLE:libc-newlib = ""
 LINKER_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 @@ 
 require conf/machine/include/riscv/arch-riscv.inc
 
-TUNEVALID[riscv64] = "Enable 64-bit RISC-V optimizations"
-TUNEVALID[riscv32] = "Enable 32-bit RISC-V optimizations"
-
-TUNEVALID[riscv64nf] = "Enable 64-bit RISC-V optimizations no floating point"
-TUNEVALID[riscv32nf] = "Enable 32-bit RISC-V optimizations no floating point"
-
-TUNEVALID[riscv64nc] = "Enable 64-bit RISC-V optimizations without compressed instructions"
-
-TUNEVALID[bigendian] = "Big endian mode"
+DEFAULTTUNE ?= "riscv64"
 
 AVAILTUNES += "riscv64 riscv32 riscv64nc riscv64nf riscv32nf"
 
 # Default
-TUNE_FEATURES:tune-riscv64 = "riscv64"
-TUNE_ARCH:tune-riscv64 = "riscv64"
-TUNE_PKGARCH:tune-riscv64 = "riscv64"
-PACKAGE_EXTRA_ARCHS:tune-riscv64 = "riscv64"
+TUNE_FEATURES:tune-riscv64 := "${@oe.tune.riscv_isa_to_tune("rv64gc")}"
+PACKAGE_EXTRA_ARCHS:tune-riscv64 = "${TUNE_RISCV_PKGARCH}"
 
-TUNE_FEATURES:tune-riscv32 = "riscv32"
-TUNE_ARCH:tune-riscv32 = "riscv32"
-TUNE_PKGARCH:tune-riscv32 = "riscv32"
-PACKAGE_EXTRA_ARCHS:tune-riscv32 = "riscv32"
+TUNE_FEATURES:tune-riscv32 := "${@oe.tune.riscv_isa_to_tune("rv32gc")}"
+PACKAGE_EXTRA_ARCHS:tune-riscv32 = "${TUNE_RISCV_PKGARCH}"
 
 # No float
-TUNE_FEATURES:tune-riscv64nf = "${TUNE_FEATURES:tune-riscv64} riscv64nf"
-TUNE_ARCH:tune-riscv64nf = "riscv64"
-TUNE_PKGARCH:tune-riscv64nf = "riscv64nf"
-PACKAGE_EXTRA_ARCHS:tune-riscv64nf = "riscv64nf"
+TUNE_FEATURES:tune-riscv64nf := "${@oe.tune.riscv_isa_to_tune("rv64imac_zicsr_zifencei")}"
+PACKAGE_EXTRA_ARCHS:tune-riscv64nf = "${TUNE_RISCV_PKGARCH}"
 
-TUNE_FEATURES:tune-riscv32nf = "${TUNE_FEATURES:tune-riscv32} riscv32nf"
-TUNE_ARCH:tune-riscv32nf = "riscv32"
-TUNE_PKGARCH:tune-riscv32nf = "riscv32nf"
-PACKAGE_EXTRA_ARCHS:tune-riscv32nf = "riscv32nf"
+TUNE_FEATURES:tune-riscv32nf := "${@oe.tune.riscv_isa_to_tune("rv32imac_zicsr_zifencei")}"
+PACKAGE_EXTRA_ARCHS:tune-riscv32nf = "${TUNE_RISCV_PKGARCH}"
 
 # no compressed
-TUNE_FEATURES:tune-riscv64nc = "${TUNE_FEATURES:tune-riscv64} riscv64nc"
-TUNE_ARCH:tune-riscv64nc = "riscv64"
-TUNE_PKGARCH:tune-riscv64nc = "riscv64nc"
-PACKAGE_EXTRA_ARCHS:tune-riscv64nc = "riscv64nc"
+TUNE_FEATURES:tune-riscv64nc := "${@oe.tune.riscv_isa_to_tune("rv64imafd_zicsr_zifencei")}"
+PACKAGE_EXTRA_ARCHS:tune-riscv64nc = "${TUNE_RISCV_PKGARCH}"
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 @@ 
 #@NAME: generic riscv32 machine
 #@DESCRIPTION: Machine configuration for running a generic riscv32
 
-require conf/machine/include/riscv/qemuriscv.inc
+DEFAULTTUNE ?= "riscv32"
 
-DEFAULTTUNE = "riscv32"
+require conf/machine/include/riscv/qemuriscv.inc
 
 PREFERRED_VERSION_openocd-native = "riscv"
 PREFERRED_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__)
 BBIMPORTS = ["qa", "data", "path", "utils", "types", "package", "packagedata", \
              "packagegroup", "sstatesig", "lsb", "cachedpath", "license", "qemu", \
              "reproducible", "rust", "buildcfg", "go", "spdx30_tasks", "spdx_common", \
-             "cve_check"]
+             "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 @@ 
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+# riscv_isa_to_tune(isa)
+#
+# Automatically translate a RISC-V ISA string to TUNE_FEATURES
+#
+# Abbreviations, such as rv32g -> rv32imaffd_zicsr_zifencei are supported.
+#
+# Profiles, such as rva22u64, are NOT supported, you must use ISA strings.
+#
+def riscv_isa_to_tune(isa):
+    _isa = isa.lower()
+
+    feature = []
+    iter = 0
+
+    # rv or riscv
+    if _isa[iter:].startswith('rv'):
+        feature.append('rv')
+        iter = iter + 2
+    elif _isa[iter:].startswith('riscv'):
+        feature.append('rv')
+        iter = iter + 5
+    else:
+        # Not a risc-v ISA!
+        return _isa
+
+    while (_isa[iter:]):
+        # Skip _ and whitespace
+        if _isa[iter] == '_' or _isa[iter].isspace():
+            iter = iter + 1
+            continue
+
+        # Length, just capture numbers here
+        if _isa[iter].isdigit():
+            iter_end = iter
+            while iter_end < len(_isa) and _isa[iter_end].isdigit():
+                iter_end = iter_end + 1
+
+            feature.append(_isa[iter:iter_end])
+            iter = iter_end
+            continue
+
+        # Typically i, e or g is next, followed by extensions.
+        # Extensions are single character, except for Z, Ss, Sh, Sm, Sv, and X
+
+        # If the extension starts with 'Z', 'S' or 'X' use the name until the next _, whitespace or end
+        if _isa[iter] in ['z', 's', 'x']:
+            ext_type = _isa[iter]
+            iter_end = iter + 1
+
+            # Multicharacter extension, these are supposed to have a _ before the next multicharacter extension
+            # See 37.4 and 37.5:
+            # 37.4: Underscores "_" may be used to separate ISA extensions...
+            # 37.5: All multi-letter extensions ... must be separated from other multi-letter extensions by an underscore...
+            # Some extensions permit only alphabetic characters, while others allow alphanumeric chartacters
+            while iter_end < len(_isa) and _isa[iter_end] != "_" and not _isa[iter_end].isspace():
+                iter_end = iter_end + 1
+
+            feature.append(_isa[iter:iter_end])
+            iter = iter_end
+            continue
+
+        # 'g' is special, it's an abbreviation for imafd_zicsr_zifencei
+        # When expanding the abbreviation, any additional letters must appear before the _z* extensions
+        if _isa[iter] == 'g':
+            _isa = 'imafd' + _isa[iter+1:] + '_zicsr_zifencei'
+            iter = 0
+            continue
+
+        feature.append(_isa[iter])
+        iter = iter + 1
+        continue
+
+    # Eliminate duplicates, but preserve the order
+    feature = list(dict.fromkeys(feature))
+    return ' '.join(feature)