diff mbox series

[RFC,v2,19/19] selftest: cases: add riscvtune.py

Message ID 20260930-riscv-tuning-rework-v2-19-9e29f3531d0e@baylibre.com
State Under Review
Headers show
Series riscv: add new ISA extensions and profile support | expand

Commit Message

Trevor Gamblin Sept. 30, 2026, 4:38 p.m. UTC
Add a test suite to ensure that all RISC-V tunes (default or 'unusual')
yield valid pkgarch combinations and that PACKAGES_EXTRA_ARCHS lists
only compatible tunings.

AI-Generated: Uses Claude Opus 5.5

Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
---
 meta/lib/oeqa/selftest/cases/riscvtune.py | 96 +++++++++++++++++++++++++++++++
 1 file changed, 96 insertions(+)
diff mbox series

Patch

diff --git a/meta/lib/oeqa/selftest/cases/riscvtune.py b/meta/lib/oeqa/selftest/cases/riscvtune.py
new file mode 100644
index 0000000000..2716b5bd02
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/riscvtune.py
@@ -0,0 +1,96 @@ 
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: MIT
+#
+
+from oe.tune import riscv_implied, riscv_isa_to_tune
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars
+
+class RiscvTuneTests(OESelftestTestCase):
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.orig_machine = get_bb_var('MACHINE')
+
+    def machine_config(self, machine):
+        # Builds set up by bitbake-setup select the machine with a builtin
+        # fragment, which is fatal alongside a MACHINE assignment, so drop it
+        return 'OE_FRAGMENTS:remove = "machine/%s"\nMACHINE = "%s"\n' % (self.orig_machine, machine)
+
+    def test_riscv_default_tunes(self):
+        """
+        Parse every RISC-V tune and check it yields a usable package arch, and
+        that PACKAGE_EXTRA_ARCHS only lists archs of other tunes whose
+        extensions are all available in this one.
+        """
+        self.write_config(self.machine_config('qemuriscv64'))
+        tunes = get_bb_var('AVAILTUNES').split()
+
+        vars = {}
+        for tune in tunes:
+            machine = 'qemuriscv32' if 'riscv32' in tune else 'qemuriscv64'
+            self.write_config(self.machine_config(machine) + 'DEFAULTTUNE = "%s"\n' % tune)
+            vars[tune] = get_bb_vars(['TUNE_FEATURES', 'TUNE_PKGARCH', 'PACKAGE_EXTRA_ARCHS', 'PACKAGE_ARCHS'])
+
+        pkgarch_features = {v['TUNE_PKGARCH']: set(v['TUNE_FEATURES'].split()) for v in vars.values()}
+
+        for tune, v in vars.items():
+            with self.subTest(tune=tune):
+                self.assertTrue(v['TUNE_FEATURES'], 'empty TUNE_FEATURES')
+                self.assertTrue(v['TUNE_PKGARCH'], 'empty TUNE_PKGARCH')
+                self.assertIn(v['TUNE_PKGARCH'], v['PACKAGE_ARCHS'].split())
+                # sstate/package names get unwieldy past this
+                self.assertLessEqual(len(v['TUNE_PKGARCH']), 64, v['TUNE_PKGARCH'])
+
+                features = set(v['TUNE_FEATURES'].split())
+                features |= riscv_implied(features)
+                for arch in v['PACKAGE_EXTRA_ARCHS'].split():
+                    self.assertIn(arch, pkgarch_features, '%s is not the package arch of any tune' % arch)
+                    self.assertLessEqual(pkgarch_features[arch], features, '%s needs extensions %s lacks' % (arch, tune))
+
+        # Profile tunes are named after the profile
+        for profile in ['rva20u64', 'rva22u64', 'rva23u64']:
+            self.assertEqual(vars[profile]['TUNE_PKGARCH'], profile + '_zifencei')
+
+        # Legacy tune names behave exactly like the tunes they alias
+        for alias, target in [('riscv64', 'riscv64gc'), ('riscv32', 'riscv32gc')]:
+            with self.subTest(alias=alias):
+                self.assertIn(alias, tunes)
+                self.assertEqual(vars[alias], vars[target])
+
+    def test_riscv_unusual_tunes(self):
+        """
+        Define custom tunes with unusual extension combinations, as a BSP
+        might, and check each one builds: the sanity checks accept the
+        package arch, gcc and binutils accept the -march string, and the
+        package arch fits in the sstate object names.
+        """
+        def profile(name, drop=None):
+            return ' '.join(f for f in riscv_isa_to_tune(name).split() if f != drop) + ' zifencei'
+        tunes = {
+            # no float (lp64 ABI), with bitmanip
+            'rv64-nofloat-bitmanip': riscv_isa_to_tune('rv64imac_zicsr_zifencei_zba_zbb_zbs'),
+            # no profile, scalar extensions on top of gc
+            'rv64gc-scalar-extras': riscv_isa_to_tune('rv64gc_zicbom_zicboz_zicond_zfa_zfhmin'),
+            # profile plus vector crypto extensions
+            'rva20-vector-crypto': profile('rva20u64') + ' v zvbb zvkt',
+            # a profile missing one extension, falling back to the previous one
+            'rva22-no-zkt': profile('rva22u64', 'zkt'),
+            # rva23u64 without the full vector extension
+            'rva23-no-v': profile('rva23u64', 'v'),
+        }
+
+        config = self.machine_config('qemuriscv64')
+        for tune, features in tunes.items():
+            config += 'AVAILTUNES += "%s"\n' % tune
+            config += 'TUNE_FEATURES:tune-%s = "%s"\n' % (tune, features)
+            config += 'PACKAGE_EXTRA_ARCHS:tune-%s = "${TUNE_RISCV_PKGARCH}"\n' % tune
+
+        for tune in tunes:
+            with self.subTest(tune=tune):
+                self.write_config(config + 'DEFAULTTUNE = "%s"\n' % tune)
+                # small, and compiled with TUNE_CCARGS. It has no do_build, and
+                # populate_sysroot also writes an sstate object.
+                bitbake('libgcc-initial -c populate_sysroot')