blob: d8eb73d445ea1de15b78b5a84c56f1c9de5decee [file] [log] [blame]
# 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
#
# https://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.
import cocotb
import tqdm
import re
import numpy as np
import os
from bazel_tools.tools.python.runfiles import runfiles
from kelvin_test_utils.sim_test_fixture import Fixture
def _get_math_result(x: np.array,
y: np.array,
symbol: str):
if symbol == 'add':
return np.add(x, y)
elif symbol == 'sub':
return np.subtract(x, y)
elif symbol == 'mul':
return np.multiply(x,y)
elif symbol == 'div':
orig_settings = np.seterr(divide='ignore')
divide_output = np.divide(x, y)
np.seterr(**orig_settings)
return divide_output
elif symbol == 'redsum':
return y[0] + np.add.reduce(x)
elif symbol == 'redmin':
return np.min(np.concatenate((x, y)))
elif symbol == 'redmax':
return np.max(np.concatenate((x, y)))
raise ValueError(f"Unsupported math symbol: {symbol}")
async def arithmetic_m1_vanilla_ops_test(dut,
dtypes,
math_ops: str,
num_bytes: int):
"""RVV arithmetic test template.
Each test performs a math op loading `in_buf_1` and `in_buf_2` and storing the output to `out_buf`.
"""
str_to_np_type ={
"int8": np.int8,
"int16": np.int16,
"int32": np.int32,
"uint8": np.uint8,
"uint16": np.uint16,
"uint32": np.uint32,
}
m1_vanilla_op_elfs = [f"rvv_{math_op}_{dtype}_m1.elf" for math_op in math_ops for dtype in dtypes]
pattern_extract = re.compile("rvv_(.*)_(.*)_m1.elf")
r = runfiles.Create()
fixture = await Fixture.Create(dut)
with tqdm.tqdm(m1_vanilla_op_elfs) as t:
for elf_name in tqdm.tqdm(m1_vanilla_op_elfs):
t.set_postfix({"binary": os.path.basename(elf_name)})
elf_path = r.Rlocation("kelvin_hw/tests/cocotb/rvv/arithmetics/" + elf_name)
await fixture.load_elf_and_lookup_symbols(
elf_path,
['in_buf_1', 'in_buf_2', 'out_buf'],
)
math_op, dtype = pattern_extract.match(elf_name).groups()
np_type = str_to_np_type[dtype]
num_values = int(num_bytes / np.dtype(np_type).itemsize)
min_value = np.iinfo(np_type).min
max_value = np.iinfo(np_type).max + 1 # One above.
input_1 = np.random.randint(min_value, max_value, num_values, dtype=np_type)
input_2 = np.random.randint(min_value, max_value, num_values, dtype=np_type)
expected_output = np.asarray(_get_math_result(input_1, input_2, math_op), dtype=np_type)
if math_op == "div":
# riscv_vdiv clobbers divide by zero with -1
# riscv_vdivu clobbers divide by zero with max value of SEW
for idx, divisor in enumerate(input_2):
if divisor == 0 and dtype[:3] == "int":
expected_output[idx] = -1
elif divisor == 0 and dtype[:4] == "uint":
expected_output[idx] = max_value - 1
await fixture.write('in_buf_1', input_1)
await fixture.write('in_buf_2', input_2)
await fixture.write('out_buf', np.zeros([num_values], dtype=np_type))
await fixture.run_to_halt()
actual_output = (await fixture.read('out_buf', num_bytes)).view(np_type)
debug_msg = str({
'input_1': input_1,
'input_2': input_2,
'expected': expected_output,
'actual': actual_output,
})
assert (actual_output == expected_output).all(), debug_msg
@cocotb.test()
async def arithmetic_m1_vanilla_ops(dut):
await arithmetic_m1_vanilla_ops_test(dut = dut,
dtypes = ["int8", "int16", "int32", "uint8", "uint16", "uint32"],
math_ops = ["add", "sub", "mul", "div"],
num_bytes = 16)
async def reduction_m1_vanilla_ops_test(dut,
dtypes,
math_ops: str,
num_bytes: int):
"""RVV reduction test template.
Each test performs a reduction op loading `in_buf_1` and storing the output to `out_buf`.
"""
str_to_np_type ={
"int8": np.int8,
"int16": np.int16,
"int32": np.int32,
"uint8": np.uint8,
"uint16": np.uint16,
"uint32": np.uint32,
}
m1_vanilla_op_elfs = [f"rvv_{math_op}_{dtype}_m1.elf" for math_op in math_ops for dtype in dtypes]
pattern_extract = re.compile("rvv_(.*)_(.*)_m1.elf")
r = runfiles.Create()
fixture = await Fixture.Create(dut)
with tqdm.tqdm(m1_vanilla_op_elfs) as t:
for elf_name in tqdm.tqdm(m1_vanilla_op_elfs):
t.set_postfix({"binary": os.path.basename(elf_name)})
elf_path = r.Rlocation(f"kelvin_hw/tests/cocotb/rvv/arithmetics/{elf_name}")
await fixture.load_elf_and_lookup_symbols(
elf_path,
['in_buf_1', 'scalar_input', 'out_buf'],
)
math_op, dtype = pattern_extract.match(elf_name).groups()
np_type = str_to_np_type[dtype]
itemsize = np.dtype(np_type).itemsize
num_values = int(num_bytes / np.dtype(np_type).itemsize)
min_value = np.iinfo(np_type).min
max_value = np.iinfo(np_type).max + 1 # One above.
input_1 = np.random.randint(min_value, max_value, num_values, dtype=np_type)
input_2 = np.random.randint(min_value, max_value, 1, dtype=np_type)
expected_output = np.asarray(_get_math_result(input_1, input_2, math_op), dtype=np_type)
await fixture.write('in_buf_1', input_1)
await fixture.write('scalar_input', input_2)
await fixture.write('out_buf', np.zeros(1, dtype=np_type))
await fixture.run_to_halt()
actual_output = (await fixture.read('out_buf', itemsize)).view(np_type)
debug_msg = str({
'input_1': input_1,
'input_2': input_2,
'expected': expected_output,
'actual': actual_output,
})
assert (actual_output == expected_output).all(), debug_msg
@cocotb.test()
async def reduction_m1_vanilla_ops(dut):
await reduction_m1_vanilla_ops_test(dut = dut,
dtypes = ["int8", "int16", "int32", "uint8", "uint16", "uint32"],
math_ops = ["redsum", "redmin", "redmax"],
num_bytes = 16)