Confirm no more rvv instructions write rd Tests are added for vcpop. Change-Id: I0178482dd8a8c7623ae14a234dddcc5bbcdee0aa
diff --git a/hdl/chisel/src/kelvin/rvv/RvvDecode.scala b/hdl/chisel/src/kelvin/rvv/RvvDecode.scala index e508492..e1d7400 100644 --- a/hdl/chisel/src/kelvin/rvv/RvvDecode.scala +++ b/hdl/chisel/src/kelvin/rvv/RvvDecode.scala
@@ -113,9 +113,8 @@ def writesRd(): Bool = { isVset() || - // OP MVV, VWXUNARY0 + // OP MVV, VWXUNARY0 (all): vmv.x.s, vcpop, vfirst. (opcode === RvvCompressedOpcode.RVVALU && funct3() === "b010".U && funct6() === "b010000".U) - // TODO(derekjchow): Add all cases that write scalar rd. } def writesVectorRegister(): Bool = {
diff --git a/kelvin_test_utils/sim_test_fixture.py b/kelvin_test_utils/sim_test_fixture.py index ccb2c82..9813d41 100644 --- a/kelvin_test_utils/sim_test_fixture.py +++ b/kelvin_test_utils/sim_test_fixture.py
@@ -48,16 +48,29 @@ async def write(self, symbol: str, data): await self.core_mini_axi.write(self.symbols[symbol], data) + async def write_word(self, symbol: str, data): + await self.core_mini_axi.write_word(self.symbols[symbol], data) + + async def write_ptr( + self, addr_symbol: str, data_symbol: str, offset: int = 0): + await self.core_mini_axi.write_word( + self.symbols[addr_symbol], self.symbols[data_symbol] + offset) + async def read(self, symbol: str, size: int): return await self.core_mini_axi.read(self.symbols[symbol], size) + async def read_word(self, symbol: str): + return await self.core_mini_axi.read_word(self.symbols[symbol]) + async def run_to_halt(self, timeout_cycles=10000): await self.core_mini_axi.execute_from(self.entry_point) - return (await self.core_mini_axi.wait_for_halted(timeout_cycles=timeout_cycles)) + return await self.core_mini_axi.wait_for_halted( + timeout_cycles=timeout_cycles) async def run_to_fault(self, timeout_cycles=10000): await self.core_mini_axi.execute_from(self.entry_point) - return (await self.core_mini_axi.wait_for_fault(timeout_cycles=timeout_cycles)) + return await self.core_mini_axi.wait_for_fault( + timeout_cycles=timeout_cycles) def fault(self): - return (self.core_mini_axi.dut.io_fault.value == 1) \ No newline at end of file + return self.core_mini_axi.dut.io_fault.value == 1
diff --git a/tests/cocotb/BUILD b/tests/cocotb/BUILD index 5c2fad2..369baeb 100644 --- a/tests/cocotb/BUILD +++ b/tests/cocotb/BUILD
@@ -160,6 +160,7 @@ "core_mini_vcsr_test", "core_mini_viota_test", "core_mini_vfirst_test", + "core_mini_vcpop_exception_test", "core_mini_vcpop_test", "core_mini_vcompress_test", "core_mini_vmsbf_test", @@ -259,6 +260,7 @@ "test_module": ["rvv_assembly_cocotb_test.py"], "deps": [ "//kelvin_test_utils:core_mini_axi_sim_interface", + "//kelvin_test_utils:sim_test_fixture", requirement("tqdm"), "@bazel_tools//tools/python/runfiles", ],
diff --git a/tests/cocotb/rvv/BUILD b/tests/cocotb/rvv/BUILD index bb767b6..70f8121 100644 --- a/tests/cocotb/rvv/BUILD +++ b/tests/cocotb/rvv/BUILD
@@ -41,6 +41,9 @@ "vfirst_test": { "srcs": ["vfirst_test.cc"], }, + "vcpop_exception_test": { + "srcs": ["vcpop_exception_test.cc"], + }, "vcpop_test": { "srcs": ["vcpop_test.cc"], }, @@ -69,6 +72,7 @@ "vill_test.elf", "viota_test.elf", "vfirst_test.elf", + "vcpop_exception_test.elf", "vcpop_test.elf", "vcompress_test.elf", "vmsbf_test.elf",
diff --git a/tests/cocotb/rvv/vcpop_exception_test.cc b/tests/cocotb/rvv/vcpop_exception_test.cc new file mode 100644 index 0000000..f520568 --- /dev/null +++ b/tests/cocotb/rvv/vcpop_exception_test.cc
@@ -0,0 +1,62 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <riscv_vector.h> +#include <stdint.h> + +uint32_t vma __attribute__((section(".data"))) = 0; +uint32_t vta __attribute__((section(".data"))) = 0; +uint32_t sew __attribute__((section(".data"))) = 0; +uint32_t lmul __attribute__((section(".data"))) = 0; +uint32_t vl __attribute__((section(".data"))) = 16; +uint32_t vstart __attribute__((section(".data"))) = 0; + +uint8_t mask_data[16] __attribute__((section(".data"))); +uint32_t result __attribute__((section(".data"))); + +uint32_t faulted __attribute__((section(".data"))) = 0; +uint32_t mcause __attribute__((section(".data"))) = 0; + +// Fault handler to log fault +extern "C" { +void kelvin_exception_handler() { + faulted = 1; + uint32_t local_mcause; + asm volatile("csrr %0, mcause" : "=r"(local_mcause)); + mcause = local_mcause; + + asm volatile("ebreak"); + while (1) { + } +} +} + +int main(int argc, char** argv) { + // Load mask data + asm volatile("vsetivli x0, 16, e8, m1, ta, ma"); + asm volatile("vle8.v v0, (%0)" : : "r"(mask_data)); + + // Set configuration state + uint32_t vtype_to_write = (vma << 7) | (vta << 6) | (sew << 3) | lmul; + asm volatile("vsetvl x0, %0, %1" : : "r"(vl), "r"(vtype_to_write)); + uint32_t local_vstart = vstart; + asm volatile("csrw vstart, %0" : : "r"(local_vstart)); + + // Run vcpop + uint32_t local_result; + asm volatile("vcpop.m %0, v0" : "=r"(local_result)); + result = local_result; + + return 0; +}
diff --git a/tests/cocotb/rvv/vcpop_test.cc b/tests/cocotb/rvv/vcpop_test.cc index 8cded24..0e3ac5f 100644 --- a/tests/cocotb/rvv/vcpop_test.cc +++ b/tests/cocotb/rvv/vcpop_test.cc
@@ -15,47 +15,49 @@ #include <riscv_vector.h> #include <stdint.h> -uint32_t vma __attribute__((section(".data"))) = 0; -uint32_t vta __attribute__((section(".data"))) = 0; -uint32_t sew __attribute__((section(".data"))) = 0; -uint32_t lmul __attribute__((section(".data"))) = 0; -uint32_t vl __attribute__((section(".data"))) = 16; -uint32_t vstart __attribute__((section(".data"))) = 0; +namespace { +constexpr size_t buf_size = 128; +} -uint8_t mask_data[16] __attribute__((section(".data"))); +size_t vl __attribute__((section(".data"))) = buf_size; +uint8_t in_buf[buf_size] __attribute__((section(".data"))); uint32_t result __attribute__((section(".data"))); +void (*impl)() __attribute__((section(".data"))) = nullptr; -uint32_t faulted __attribute__((section(".data"))) = 0; -uint32_t mcause __attribute__((section(".data"))) = 0; - -// Fault handler to log fault extern "C" { -void kelvin_exception_handler() { - faulted = 1; - uint32_t local_mcause; - asm volatile("csrr %0, mcause" : "=r"(local_mcause)); - mcause = local_mcause; +__attribute__((used, retain)) void vcpop_m_b1() { + auto data = __riscv_vlm_v_b1(in_buf, vl); + result = __riscv_vcpop_m_b1(data, vl); +} - asm volatile("ebreak"); - while (1) {} +__attribute__((used, retain)) void vcpop_m_b2() { + auto data = __riscv_vlm_v_b2(in_buf, vl); + result = __riscv_vcpop_m_b2(data, vl); +} + +__attribute__((used, retain)) void vcpop_m_b4() { + auto data = __riscv_vlm_v_b4(in_buf, vl); + result = __riscv_vcpop_m_b4(data, vl); +} + +__attribute__((used, retain)) void vcpop_m_b8() { + auto data = __riscv_vlm_v_b8(in_buf, vl); + result = __riscv_vcpop_m_b8(data, vl); +} + +__attribute__((used, retain)) void vcpop_m_b16() { + auto data = __riscv_vlm_v_b16(in_buf, vl); + result = __riscv_vcpop_m_b16(data, vl); +} + +__attribute__((used, retain)) void vcpop_m_b32() { + auto data = __riscv_vlm_v_b32(in_buf, vl); + result = __riscv_vcpop_m_b32(data, vl); } } -int main(int argc, char **argv) { - // Load mask data - asm volatile("vsetivli x0, 16, e8, m1, ta, ma"); - asm volatile("vle8.v v0, (%0)" : : "r"(mask_data)); - - // Set configuration state - uint32_t vtype_to_write = (vma << 7) | (vta << 6) | (sew << 3) | lmul; - asm volatile("vsetvl x0, %0, %1": : "r"(vl), "r"(vtype_to_write)); - uint32_t local_vstart = vstart; - asm volatile("csrw vstart, %0" : : "r"(local_vstart)); - - // Run vcpop - uint32_t local_result; - asm volatile("vcpop.m %0, v0" : "=r"(local_result)); - result = local_result; +int main(int argc, char** argv) { + impl(); return 0; -} \ No newline at end of file +}
diff --git a/tests/cocotb/rvv_assembly_cocotb_test.py b/tests/cocotb/rvv_assembly_cocotb_test.py index d66d356..ca7ee80 100644 --- a/tests/cocotb/rvv_assembly_cocotb_test.py +++ b/tests/cocotb/rvv_assembly_cocotb_test.py
@@ -1,6 +1,7 @@ import cocotb import numpy as np from kelvin_test_utils.core_mini_axi_interface import CoreMiniAxiInterface +from kelvin_test_utils.sim_test_fixture import Fixture from bazel_tools.tools.python.runfiles import runfiles SEWS = [ @@ -271,10 +272,60 @@ @cocotb.test() -async def core_mini_vcpop_test(dut): +async def core_mini_vcpop_exception_test(dut): """Testbench to test vstart!=0 vcpop.""" await test_vstart_not_zero_failure( - dut, "kelvin_hw/tests/cocotb/rvv/vcpop_test.elf") + dut, "kelvin_hw/tests/cocotb/rvv/vcpop_exception_test.elf") + + +@cocotb.test() +async def core_mini_vcpop_test(dut): + """Test vcpop usage accessible from intrinsics.""" + # mask is not accessible from here. + fixture = await Fixture.Create(dut) + r = runfiles.Create() + cases = [ + # lmul>1 currently fail + # {'impl': 'vcpop_m_b1', 'vl': 1024}, + # {'impl': 'vcpop_m_b2', 'vl': 512}, + # {'impl': 'vcpop_m_b4', 'vl': 256}, + {'impl': 'vcpop_m_b8', 'vl': 128}, + {'impl': 'vcpop_m_b16', 'vl': 64}, + {'impl': 'vcpop_m_b32', 'vl': 32}, + ] + await fixture.load_elf_and_lookup_symbols( + r.Rlocation('kelvin_hw/tests/cocotb/rvv/vcpop_test.elf'), + ['vl', 'in_buf', 'result', 'impl'] + [c['impl'] for c in cases], + ) + rng = np.random.default_rng() + for c in cases: + impl = c['impl'] + vl = c['vl'] + # TODO(davidgao): test other vl. Need special handling of expected + # output if not full bytes. + in_bytes = vl // 8 + + input_data = rng.integers( + low=0, high=256, size=in_bytes, dtype=np.uint8) + expected_output = np.sum( + np.bitwise_count(input_data), dtype=np.uint32) + + await fixture.write_ptr('impl', impl) + await fixture.write_word('vl', vl) + await fixture.write('in_buf', input_data) + await fixture.write_word('result', 0) + + await fixture.run_to_halt() + + actual_output = (await fixture.read_word('result')).view(np.uint32) + + debug_msg = str({ + 'impl': impl, + 'input': input_data, + 'expected': expected_output, + 'actual': actual_output, + }) + assert (actual_output == expected_output), debug_msg @cocotb.test()