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
© 2016 - 2026 Red Hat, Inc.