[PATCH 5/5] target/riscv: Add zvl*b extension support in arch= property

Kito Cheng posted 5 patches 1 month ago
Maintainers: Palmer Dabbelt <palmer@dabbelt.com>, Alistair Francis <alistair.francis@wdc.com>, Weiwei Li <liwei1518@gmail.com>, Daniel Henrique Barboza <dbarboza@ventanamicro.com>, Liu Zhiwei <zhiwei_liu@linux.alibaba.com>
[PATCH 5/5] target/riscv: Add zvl*b extension support in arch= property
Posted by Kito Cheng 1 month ago
Add support for specifying vector length (VLEN) using zvl*b extensions
in the arch= property. This allows explicit VLEN configuration when
using ISA strings or profiles.

Examples:
  -cpu rv64,arch=rv64gcv_zvl256b       # V with VLEN=256
  -cpu rv64,arch=rv64i_zve64f_zvl128b  # Zve64f with VLEN=128
  -cpu rv64,arch=rva23u64_zvl512b      # RVA23 profile with VLEN=512

Key features:
- zvl<N>b where N is VLEN in bits (must be power of 2, 32-65536)
- Valid extensions: zvl32b, zvl64b, zvl128b, zvl256b, zvl512b, etc.
- zvl*b requires v or zve* extension to also be specified
- Multiple zvl*b extensions take the maximum value
- Extensions imply minimum VLEN: v implies 128, zve64* implies 64,
  zve32* implies 32

The arch=dump output now shows:
- Vector length configuration (VLEN=N bits)
- zvl<N>b in the full ISA string

The arch=help output now lists available zvl*b extensions with
documentation.

Signed-off-by: Kito Cheng <kito.cheng@sifive.com>
---
 docs/system/target-riscv.rst              |   2 +
 target/riscv/cpu.c                        |  23 ++++-
 target/riscv/tcg/tcg-cpu.c                | 116 +++++++++++++++++++++-
 tests/functional/riscv64/test_cpu_arch.py |  68 +++++++++++++
 4 files changed, 207 insertions(+), 2 deletions(-)

diff --git a/docs/system/target-riscv.rst b/docs/system/target-riscv.rst
index 12807974a8a..6734c86848a 100644
--- a/docs/system/target-riscv.rst
+++ b/docs/system/target-riscv.rst
@@ -134,6 +134,8 @@ extensions:
   Special extensions:
 
   - ``g`` expands to ``imafd_zicsr_zifencei`` (General purpose)
+  - ``zvl<N>b`` specifies vector length (VLEN) in bits, where N must
+    be a power of 2 (e.g., ``zvl128b``, ``zvl256b``, ``zvl512b``)
 
   The ISA string must match the CPU's XLEN. For example, ``arch=rv32i`` will
   fail on a 64-bit CPU.
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 092635b1050..8c3a0c94483 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -1028,6 +1028,11 @@ G_NORETURN void riscv_cpu_list_supported_extensions(void)
     riscv_cpu_help_multiext("Experimental Extensions",
                             riscv_cpu_experimental_exts);
 
+    /* Print vector length extensions */
+    qemu_printf("Vector Length Extensions (zvl*b):\n");
+    qemu_printf("  zvl32b, zvl64b, zvl128b, zvl256b, zvl512b, zvl1024b, ...\n");
+    qemu_printf("  (Specifies VLEN in bits, must be power of 2)\n\n");
+
     /* Print available profiles */
     qemu_printf("Profiles (64-bit only):\n");
     for (int i = 0; riscv_profiles[i] != NULL; i++) {
@@ -1062,7 +1067,14 @@ static G_NORETURN void riscv_cpu_dump_isa_config(RISCVCPU *cpu)
 
     /* Print base information */
     qemu_printf("Base: RV%d\n", xlen);
-    qemu_printf("Privilege spec: %s\n\n", priv_spec_to_str(env->priv_ver));
+    qemu_printf("Privilege spec: %s\n", priv_spec_to_str(env->priv_ver));
+
+    /* Print vector length configuration */
+    if (cpu->cfg.vlenb > 0) {
+        uint16_t vlen = cpu->cfg.vlenb << 3;
+        qemu_printf("Vector length: VLEN=%u bits (zvl%ub)\n", vlen, vlen);
+    }
+    qemu_printf("\n");
 
     /* Print single-letter extensions */
     qemu_printf("Standard Extensions (single-letter):\n");
@@ -3053,6 +3065,15 @@ static void riscv_isa_string_ext(RISCVCPU *cpu, char **isa_str,
         }
     }
 
+    /* Add zvl*b if vector length is configured */
+    if (cpu->cfg.vlenb > 0) {
+        uint16_t vlen = cpu->cfg.vlenb << 3;
+        g_autofree char *zvl_ext = g_strdup_printf("zvl%ub", vlen);
+        new = g_strconcat(old, "_", zvl_ext, NULL);
+        g_free(old);
+        old = new;
+    }
+
     *isa_str = new;
 }
 
diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c
index aa947337cf1..03a748b7dcc 100644
--- a/target/riscv/tcg/tcg-cpu.c
+++ b/target/riscv/tcg/tcg-cpu.c
@@ -1596,6 +1596,73 @@ static void riscv_cpu_disable_all_extensions(RISCVCPU *cpu)
     for (const RISCVIsaExtData *edata = isa_edata_arr; edata->name; edata++) {
         isa_ext_update_enabled(cpu, edata->ext_enable_offset, false);
     }
+
+    /* Reset vector length to 0 (will be set by zvl*b or implied by zve/v) */
+    cpu->cfg.vlenb = 0;
+}
+
+/*
+ * Parse zvl*b extension name and return the minimum VLEN in bits.
+ * Returns 0 if the extension name is not a valid zvl*b pattern.
+ * Valid patterns: zvl32b, zvl64b, zvl128b, zvl256b, zvl512b, zvl1024b, etc.
+ */
+static int riscv_parse_zvl_vlen(const char *ext_name)
+{
+    int vlen;
+    char suffix;
+
+    if (g_ascii_strncasecmp(ext_name, "zvl", 3) != 0) {
+        return 0;
+    }
+
+    if (sscanf(ext_name + 3, "%d%c", &vlen, &suffix) != 2) {
+        return 0;
+    }
+
+    if (g_ascii_tolower(suffix) != 'b') {
+        return 0;
+    }
+
+    /* Validate VLEN is a power of 2 and within reasonable range */
+    if (vlen < 32 || vlen > 65536 || (vlen & (vlen - 1)) != 0) {
+        return 0;
+    }
+
+    return vlen;
+}
+
+/*
+ * Get the implied minimum VLEN (in bits) for an extension.
+ * Returns 0 if the extension doesn't imply a minimum VLEN.
+ *
+ * According to RISC-V specification:
+ * - zve32x, zve32f imply zvl32b (VLEN >= 32)
+ * - zve64x, zve64f, zve64d imply zvl64b (VLEN >= 64)
+ * - v implies zvl128b (VLEN >= 128)
+ */
+static int riscv_ext_implied_vlen(const char *ext_name)
+{
+    if (g_ascii_strcasecmp(ext_name, "v") == 0) {
+        return 128;
+    }
+    if (g_ascii_strncasecmp(ext_name, "zve64", 5) == 0) {
+        return 64;
+    }
+    if (g_ascii_strncasecmp(ext_name, "zve32", 5) == 0) {
+        return 32;
+    }
+    return 0;
+}
+
+/*
+ * Update vlenb if the new VLEN is larger than the current one.
+ */
+static void riscv_update_vlen(RISCVCPU *cpu, int vlen)
+{
+    uint16_t current_vlen = cpu->cfg.vlenb << 3;
+    if (vlen > current_vlen) {
+        cpu->cfg.vlenb = vlen >> 3;
+    }
 }
 
 /*
@@ -1700,6 +1767,9 @@ static bool riscv_cpu_parse_isa_string(RISCVCPU *cpu, const char *isa_str,
 
             uint32_t bit = riscv_get_misa_bit_from_name(ext_char);
             riscv_cpu_write_misa_bit(cpu, bit, true);
+            if (ext_char == 'v') {
+                riscv_update_vlen(cpu, 128);
+            }
             is_first_ext = false;
         }
 
@@ -1711,6 +1781,13 @@ static bool riscv_cpu_parse_isa_string(RISCVCPU *cpu, const char *isa_str,
         /* Process the remaining multi-letter extension */
         const char *multi_ext = ext_name + single_count;
 
+        /* Check for zvl*b extension (vector length) */
+        int zvl_vlen = riscv_parse_zvl_vlen(multi_ext);
+        if (zvl_vlen > 0) {
+            riscv_update_vlen(cpu, zvl_vlen);
+            continue;
+        }
+
         /* Look up the extension */
         const RISCVIsaExtData *edata = riscv_find_ext_data(multi_ext);
         if (edata == NULL) {
@@ -1720,6 +1797,29 @@ static bool riscv_cpu_parse_isa_string(RISCVCPU *cpu, const char *isa_str,
 
         /* Enable the extension */
         isa_ext_update_enabled(cpu, edata->ext_enable_offset, true);
+
+        /* Check for implied minimum VLEN (zve32*, zve64*) */
+        int implied_vlen = riscv_ext_implied_vlen(multi_ext);
+        if (implied_vlen > 0) {
+            riscv_update_vlen(cpu, implied_vlen);
+        }
+    }
+
+    /*
+     * Validate that zvl*b is only specified with a vector extension.
+     * zvl*b requires v or zve* extension to also be specified.
+     */
+    if (cpu->cfg.vlenb > 0) {
+        CPURISCVState *env = &cpu->env;
+        bool has_vector = (env->misa_ext & RVV) ||
+                          cpu->cfg.ext_zve32x || cpu->cfg.ext_zve32f ||
+                          cpu->cfg.ext_zve64x || cpu->cfg.ext_zve64f ||
+                          cpu->cfg.ext_zve64d;
+        if (!has_vector) {
+            error_setg(errp, "zvl*b requires v or zve* extension to also be "
+                       "specified");
+            return false;
+        }
     }
 
     return true;
@@ -1799,6 +1899,13 @@ static bool riscv_cpu_parse_profile_string(RISCVCPU *cpu, const char *str,
         size_t ext_len = p - ext_start;
         g_autofree char *ext_name = g_strndup(ext_start, ext_len);
 
+        /* Check for zvl*b extension */
+        int zvl_vlen = riscv_parse_zvl_vlen(ext_name);
+        if (zvl_vlen > 0) {
+            riscv_update_vlen(cpu, zvl_vlen);
+            continue;
+        }
+
         /* Look up the extension */
         const RISCVIsaExtData *edata = riscv_find_ext_data(ext_name);
         if (edata == NULL) {
@@ -1809,6 +1916,12 @@ static bool riscv_cpu_parse_profile_string(RISCVCPU *cpu, const char *str,
 
         /* Enable the extension */
         isa_ext_update_enabled(cpu, edata->ext_enable_offset, true);
+
+        /* Check for implied minimum VLEN */
+        int implied_vlen = riscv_ext_implied_vlen(ext_name);
+        if (implied_vlen > 0) {
+            riscv_update_vlen(cpu, implied_vlen);
+        }
     }
 
     return true;
@@ -1856,7 +1969,8 @@ static void riscv_cpu_add_arch_property(Object *obj)
         "ISA configuration (write-only). "
         "Use 'help' to list extensions, 'dump' to show current config, "
         "provide an ISA string (e.g., rv64gc_zba_zbb), "
-        "or use a profile (e.g., rva23u64).");
+        "or a profile with optional extensions (e.g., rva23u64, "
+        "rva23u64_zbkb_zkne).");
 }
 
 /*
diff --git a/tests/functional/riscv64/test_cpu_arch.py b/tests/functional/riscv64/test_cpu_arch.py
index c12e1c7fce4..b1f02db9dd2 100644
--- a/tests/functional/riscv64/test_cpu_arch.py
+++ b/tests/functional/riscv64/test_cpu_arch.py
@@ -266,6 +266,13 @@ def test_arch_isa_string_underscore_separated_single(self):
         self.assertRegex(res.stdout, r'd\s+enabled')
         self.assertRegex(res.stdout, r'c\s+enabled')
 
+    def test_arch_isa_string_zvl_requires_vector(self):
+        """Test zvl*b requires v or zve* extension"""
+        res = self.run_qemu('rv64,arch=rv64g_zvl128b')
+
+        self.assertNotEqual(res.returncode, 0)
+        self.assertIn("zvl*b requires v or zve* extension", res.stderr)
+
     def test_arch_profile_rva23u64(self):
         """Test arch=rva23u64 enables RVA23 profile extensions"""
         res = self.run_qemu('rv64,arch=rva23u64,arch=dump')
@@ -338,6 +345,67 @@ def test_arch_profile_with_unknown_extension(self):
         self.assertNotEqual(res.returncode, 0)
         self.assertIn("unknown extension 'unknown'", res.stderr)
 
+    def test_arch_help_shows_zvl(self):
+        """Test arch=help lists zvl*b extensions"""
+        res = self.run_qemu('rv64,arch=help')
+
+        self.assertEqual(res.returncode, 0)
+        self.assertIn('Vector Length Extensions', res.stdout)
+        self.assertIn('zvl32b', res.stdout)
+        self.assertIn('zvl128b', res.stdout)
+
+    def test_arch_isa_string_zvl(self):
+        """Test arch=ISA-STRING accepts zvl*b extensions"""
+        res = self.run_qemu('rv64,arch=rv64gcv_zvl256b,arch=dump')
+
+        self.assertEqual(res.returncode, 0)
+        self.assertIn('VLEN=256', res.stdout)
+        self.assertIn('zvl256b', res.stdout)
+        # Check zvl*b is included in Full ISA string
+        self.assertRegex(res.stdout, r'Full ISA string:.*_zvl256b')
+
+    def test_arch_dump_shows_vlen(self):
+        """Test arch=dump shows vector length configuration"""
+        res = self.run_qemu('rv64,arch=rv64gcv_zvl512b,arch=dump')
+
+        self.assertEqual(res.returncode, 0)
+        self.assertIn('Vector length:', res.stdout)
+        self.assertIn('VLEN=512', res.stdout)
+
+    def test_arch_isa_string_zvl_takes_max(self):
+        """Test multiple zvl*b extensions take maximum value"""
+        # zvl128b followed by zvl512b - should use 512
+        res1 = self.run_qemu('rv64,arch=rv64gcv_zvl128b_zvl512b,arch=dump')
+        self.assertEqual(res1.returncode, 0)
+        self.assertIn('VLEN=512', res1.stdout)
+
+        # zvl512b followed by zvl128b - should still use 512
+        res2 = self.run_qemu('rv64,arch=rv64gcv_zvl512b_zvl128b,arch=dump')
+        self.assertEqual(res2.returncode, 0)
+        self.assertIn('VLEN=512', res2.stdout)
+
+        # Three zvl extensions - should use maximum (1024)
+        res3 = self.run_qemu('rv64,arch=rv64gcv_zvl256b_zvl1024b_zvl512b,arch=dump')
+        self.assertEqual(res3.returncode, 0)
+        self.assertIn('VLEN=1024', res3.stdout)
+
+    def test_arch_isa_string_implied_vlen(self):
+        """Test extensions imply minimum VLEN correctly"""
+        # zve64f implies zvl64b, so zvl32b should be ignored
+        res1 = self.run_qemu('rv64,arch=rv64i_zve64f_zvl32b,arch=dump')
+        self.assertEqual(res1.returncode, 0)
+        self.assertIn('VLEN=64', res1.stdout)
+
+        # v implies zvl128b, so zvl64b should be ignored
+        res2 = self.run_qemu('rv64,arch=rv64gcv_zvl64b,arch=dump')
+        self.assertEqual(res2.returncode, 0)
+        self.assertIn('VLEN=128', res2.stdout)
+
+        # zve64x alone should have VLEN=64
+        res3 = self.run_qemu('rv64,arch=rv64i_zve64x,arch=dump')
+        self.assertEqual(res3.returncode, 0)
+        self.assertIn('VLEN=64', res3.stdout)
+
 
 if __name__ == '__main__':
     QemuUserTest.main()
-- 
2.52.0