Improve cocotb Bazel rules
- Introduces cocotb_test_suite, which defines a test suite that can
target both Verilator and VCS from one rule. This rule creates a
target which will run the whole suite, and then also accepts a
testcases list which will be used to create single-test targets.
- Add utils/update_all_cocotb_tests.py to keep the aforementioned
testcase lists up-to-date.
- Rules generated by this way are highly queriable to find lists of
tests to run, e.g.
For finding all suites that use the RvvCoreMiniAxi HDL on VCS:
- bazel query 'kind("cocotb_test", //...) intersect attr("tags", "vcs_cocotb_test_suite", //...) intersect attr("hdl_toplevel", "^RvvCoreMiniAxi$", //...)'
For finding all individual tests that use the RvvCoreMiniAxi HDL on VCS:
- bazel query 'kind("cocotb_test", //...) intersect attr("tags", "vcs_cocotb_single_test", //...) intersect attr("hdl_toplevel", "^RvvCoreMiniAxi$", //...)'
Change-Id: I5c639b703c3c062ef37480d3aac32418761fcab2
diff --git a/kelvin_test_utils/core_mini_axi_pyocd_gdbserver.py b/kelvin_test_utils/core_mini_axi_pyocd_gdbserver.py
index cad8813..4dfa085 100644
--- a/kelvin_test_utils/core_mini_axi_pyocd_gdbserver.py
+++ b/kelvin_test_utils/core_mini_axi_pyocd_gdbserver.py
@@ -17,6 +17,7 @@
import tempfile
import threading
+from bazel_tools.tools.python.runfiles import runfiles
from enum import Enum
from cocotb.triggers import ClockCycles
from pyocd.board.board import Board
@@ -349,6 +350,8 @@
def exec_gdb():
with tempfile.NamedTemporaryFile(mode='w+') as cmdfile:
+ r = runfiles.Create()
+ gdb_path = r.Rlocation("kelvin_hw/toolchain/gdb")
cmds_pre = [
'set architecture riscv:rv32',
'target remote :3333',
@@ -361,7 +364,7 @@
cmdfile.write(f'{cmd}\n')
cmdfile.flush()
args = [
- '../../../toolchain/gdb',
+ gdb_path,
'-x',
cmdfile.name,
elf.name,
diff --git a/rules/coco_tb.bzl b/rules/coco_tb.bzl
index 229b7ca..e746cb5 100644
--- a/rules/coco_tb.bzl
+++ b/rules/coco_tb.bzl
@@ -12,13 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Convinence wrapper for Verilator driven cocotb."""
+"""Convenience wrapper for Verilator driven cocotb."""
load("@kelvin_hw//third_party/python:requirements.bzl", "requirement")
load("@rules_hdl//cocotb:cocotb.bzl", "cocotb_test")
load("@rules_python//python:defs.bzl", "py_library")
def _verilator_cocotb_model_impl(ctx):
+ """Implementation of the verilator_cocotb_model rule."""
cc_toolchain = ctx.toolchains["@bazel_tools//tools/cpp:toolchain_type"].cc
ar_executable = cc_toolchain.ar_executable
compiler_executable = cc_toolchain.compiler_executable
@@ -89,7 +90,7 @@
return [
DefaultInfo(
files = depset([output_file, make_log]),
- runfiles = ctx.runfiles(files=[output_file, make_log]),
+ runfiles = ctx.runfiles(files = [output_file, make_log]),
executable = output_file,
),
OutputGroupInfo(
@@ -98,6 +99,19 @@
]
verilator_cocotb_model = rule(
+ doc = """Builds a verilator model for cocotb.
+
+ This rule takes a verilog source file and a toplevel module name and
+ builds a verilator model that can be used with cocotb.
+
+ It returns a DefaultInfo provider with an executable that can be run
+ to execute the simulation.
+
+ Attributes:
+ verilog_source: The verilog source file to build the model from.
+ hdl_toplevel: The name of the toplevel module.
+ cflags: A list of flags to pass to the compiler.
+ """,
implementation = _verilator_cocotb_model_impl,
attrs = {
"verilog_source": attr.label(allow_single_file = True, mandatory = True),
@@ -127,15 +141,30 @@
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
)
-def verilator_cocotb_test(name,
- model,
- hdl_toplevel,
- test_module,
- deps=[],
- data=[],
- **kwargs):
+def verilator_cocotb_test(
+ name,
+ model,
+ hdl_toplevel,
+ test_module,
+ deps = [],
+ data = [],
+ **kwargs):
+ """Runs a cocotb test with a verilator model.
+
+ This is a wrapper around the cocotb_test rule that is specific to
+ verilator.
+
+ Args:
+ name: The name of the test.
+ model: The verilator_cocotb_model target to use.
+ hdl_toplevel: The name of the toplevel module.
+ test_module: The python module that contains the test.
+ deps: Additional dependencies for the test.
+ data: Data dependencies for the test.
+ **kwargs: Additional arguments to pass to the cocotb_test rule.
+ """
kwargs.update(
- hdl_toplevel_lang="verilog",
+ hdl_toplevel_lang = "verilog",
sim_name = "verilator",
sim = [
"@verilator//:verilator",
@@ -163,23 +192,89 @@
deps = [
":{}_test_data".format(name),
],
- **kwargs,
+ **kwargs
)
-def vcs_cocotb_test(name,
- hdl_toplevel,
- test_module,
- testcases=[],
- deps=[],
- data=[],
- **kwargs):
- tags = kwargs.pop("tags", [])
+def _verilator_cocotb_test_suite(
+ name,
+ model,
+ testcases = [],
+ testcases_vname = "",
+ tests_kwargs = {},
+ **kwargs):
+ """Runs a cocotb test with a verilator model.
+
+ This is a wrapper around the cocotb_test rule that is specific to
+ verilator.
+
+ Args:
+ name: The name of the test.
+ model: The verilator_cocotb_model target to use.
+ testcases: A list of testcases to run. A test will be generated for each
+ testcase.
+ tests_kwargs: A dictionary of arguments to pass to the cocotb_test rule.
+ **kwargs: Additional arguments to pass to the cocotb_test rule.
+ """
+ all_tests_kwargs = dict(tests_kwargs)
+ all_tests_kwargs.update(kwargs)
+
+ if testcases:
+ test_targets = []
+ for tc in testcases:
+ tc_tests_kwargs = dict(all_tests_kwargs)
+ tags = list(tc_tests_kwargs.pop("tags", []))
+ tags.append("manual")
+ tags.append("verilator_cocotb_single_test")
+ verilator_cocotb_test(
+ name = "{}_{}".format(name, tc),
+ model = model,
+ testcase = [tc],
+ tags = tags,
+ **tc_tests_kwargs
+ )
+ test_targets.append(":{}_{}".format(name, tc))
+
+ # Generate a meta-target for all tests.
+ meta_target_kwargs = dict(all_tests_kwargs)
+ tags = list(meta_target_kwargs.pop("tags", []))
+ tags.append("verilator_cocotb_test_suite")
+ if testcases_vname:
+ tags.append("testcases_vname={}".format(testcases_vname))
+ verilator_cocotb_test(
+ name = name,
+ model = model,
+ tags = tags,
+ **meta_target_kwargs
+ )
+
+def vcs_cocotb_test(
+ name,
+ hdl_toplevel,
+ test_module,
+ deps = [],
+ data = [],
+ **kwargs):
+ """Runs a cocotb test with a vcs model.
+
+ This is a wrapper around the cocotb_test rule that is specific to
+ vcs.
+
+ Args:
+ name: The name of the test.
+ hdl_toplevel: The name of the toplevel module.
+ test_module: The python module that contains the test.
+ deps: Additional dependencies for the test.
+ data: Data dependencies for the test.
+ **kwargs: Additional arguments to pass to the cocotb_test rule.
+ """
+ tags = list(kwargs.pop("tags", []))
tags.append("vcs")
kwargs.update(
- hdl_toplevel_lang="verilog",
+ hdl_toplevel_lang = "verilog",
sim_name = "vcs",
sim = [],
- tags = tags)
+ tags = tags,
+ )
# Wrap in py_library so we can forward data
py_library(
@@ -193,15 +288,133 @@
data = data,
)
- test_args = kwargs.pop("test_args", [""])
- [cocotb_test(
- name = "{}_{}".format(name, tc),
+ cocotb_test(
+ name = name,
hdl_toplevel = hdl_toplevel,
test_module = test_module,
- testcase = [tc],
- test_args = ["{} -cm_name {}".format(test_args[0], tc)] + test_args[1:],
deps = [
":{}_test_data".format(name),
],
- **kwargs,
- ) for tc in testcases]
+ **kwargs
+ )
+
+def _vcs_cocotb_test_suite(
+ name,
+ verilog_sources,
+ testcases = [],
+ testcases_vname = "",
+ tests_kwargs = {},
+ **kwargs):
+ """Runs a cocotb test with a vcs model.
+
+ This is a wrapper around the cocotb_test rule that is specific to
+ vcs.
+
+ Args:
+ name: The name of the test.
+ verilog_sources: The verilog sources to use for the test.
+ testcases: A list of testcases to run. A test will be generated for each
+ testcase.
+ tests_kwargs: A dictionary of arguments to pass to the cocotb_test rule.
+ **kwargs: Additional arguments to pass to the cocotb_test rule.
+ """
+ all_tests_kwargs = dict(tests_kwargs)
+ all_tests_kwargs.update(kwargs)
+
+ hdl_toplevel = all_tests_kwargs.get("hdl_toplevel")
+ if not hdl_toplevel:
+ fail("hdl_toplevel must be specified in tests_kwargs")
+
+ if testcases:
+ test_targets = []
+ for tc in testcases:
+ tc_tests_kwargs = dict(all_tests_kwargs)
+ tags = list(tc_tests_kwargs.pop("tags", []))
+ tags.append("manual")
+ tags.append("vcs_cocotb_single_test")
+ test_args = tc_tests_kwargs.pop("test_args", [""])
+ vcs_cocotb_test(
+ name = "{}_{}".format(name, tc),
+ testcase = [tc],
+ tags = tags,
+ test_args = ["{} -cm_name {}".format(test_args[0], tc)] + test_args[1:],
+ verilog_sources = verilog_sources,
+ **tc_tests_kwargs
+ )
+ test_targets.append(":{}_{}".format(name, tc))
+
+ # Generate a meta-target for all tests.
+ meta_target_kwargs = dict(all_tests_kwargs)
+ tags = list(meta_target_kwargs.pop("tags", []))
+ tags.append("vcs_cocotb_test_suite")
+ vcs_cocotb_test(
+ name = name,
+ tags = tags,
+ verilog_sources = verilog_sources,
+ **meta_target_kwargs
+ )
+
+def cocotb_test_suite(name, testcases, simulators = ["verilator"], **kwargs):
+ """Runs a cocotb test with a verilator or vcs model.
+
+ This is a wrapper around the cocotb_test rule that is specific to
+ verilator.
+
+ Args:
+ name: The name of the test.
+ simulators: A list of simulators to run the test with.
+ Supported simulators are "verilator" and "vcs".
+ **kwargs: Additional arguments to pass to the cocotb_test rule.
+ These can be prefixed with the simulator name to apply them to
+ only that simulator.
+ """
+
+ # Pop tests_kwargs from kwargs, if it exists.
+ tests_kwargs = kwargs.pop("tests_kwargs", {})
+ testcases_vname = kwargs.pop("testcases_vname", "")
+ for sim in simulators:
+ sim_kwargs = {}
+ sim_tests_kwargs = dict(tests_kwargs)
+
+ # Partition kwargs into sim_kwargs
+ for key, value in kwargs.items():
+ if key.startswith(sim):
+ sim_kwargs[key.replace(sim + "_", "")] = value
+
+ # Partition tests_kwargs into sim_tests_kwargs
+ for key, value in tests_kwargs.items():
+ if key.startswith(sim):
+ sim_tests_kwargs[key.replace(sim + "_", "")] = value
+
+ # Remove sim-specific kwargs from tests_kwargs
+ for key, value in tests_kwargs.items():
+ if key.startswith(sim):
+ if key in sim_tests_kwargs:
+ sim_tests_kwargs.pop(key)
+
+ if sim == "verilator":
+ model = sim_kwargs.pop("model", None)
+ if not model:
+ fail("verilator_model must be specified for verilator tests")
+ _verilator_cocotb_test_suite(
+ name = name,
+ model = model,
+ testcases = testcases,
+ testcases_vname = testcases_vname,
+ tests_kwargs = sim_tests_kwargs,
+ **sim_kwargs
+ )
+ elif sim == "vcs":
+ verilog_sources = sim_kwargs.pop("verilog_sources", [])
+ if not verilog_sources:
+ fail("vcs_verilog_sources must be specified for vcs tests")
+ _vcs_cocotb_test_suite(
+ name = "{}_{}".format(sim, name),
+ verilog_sources = verilog_sources,
+ testcases = testcases,
+ testcases_vname = testcases_vname,
+ tests_kwargs = sim_tests_kwargs,
+ **sim_kwargs
+ )
+ else:
+ fail("Unknown simulator: {}".format(sim))
\ No newline at end of file
diff --git a/tests/cocotb/BUILD b/tests/cocotb/BUILD
index f27a000..673daad 100644
--- a/tests/cocotb/BUILD
+++ b/tests/cocotb/BUILD
@@ -12,109 +12,56 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-load("//rules:coco_tb.bzl", "vcs_cocotb_test", "verilator_cocotb_test", "verilator_cocotb_model")
-load("//rules:kelvin_v2.bzl", "kelvin_v2_binary")
-load("//rules:utils.bzl", "template_rule")
load("@kelvin_hw//third_party/python:requirements.bzl", "requirement")
load("@rules_pkg//:pkg.bzl", "pkg_zip")
+load("//rules:coco_tb.bzl", "cocotb_test_suite", "verilator_cocotb_model")
+load("//rules:kelvin_v2.bzl", "kelvin_v2_binary")
+load("//rules:utils.bzl", "template_rule")
+
+load(
+ "//tests/cocotb:build_defs.bzl",
+ "VCS_BUILD_ARGS",
+ "VCS_DEFINES",
+ "VCS_TEST_ARGS",
+ "VERILATOR_BUILD_ARGS",
+)
package(default_visibility = ["//visibility:public"])
COCOTB_TEST_BINARY_TARGETS = glob(["**/*.elf"]) + glob(["**/*.o"]) + [
- ":align_test.elf",
- ":float_csr_interlock_test.elf",
- ":finish_txn_before_halt.elf",
- ":stress_test.elf",
- ":wfi_slot_0.elf",
- ":wfi_slot_1.elf",
- ":wfi_slot_2.elf",
- ":wfi_slot_3.elf",
- ]
-
-VERILATOR_BUILD_ARGS = [
- "-Wno-WIDTH",
- "-Wno-CASEINCOMPLETE",
- "-Wno-LATCH",
- "-Wno-SIDEEFFECT",
- "-Wno-MULTIDRIVEN",
- "-Wno-UNOPTFLAT",
- # Warnings that we disable for fpnew
- "-Wno-ASCRANGE",
- "-Wno-WIDTHEXPAND",
- "-Wno-WIDTHTRUNC",
- "-Wno-UNSIGNED",
- "-DUSE_GENERIC=\"\"",
+ ":align_test.elf",
+ ":float_csr_interlock_test.elf",
+ ":finish_txn_before_halt.elf",
+ ":stress_test.elf",
+ ":wfi_slot_0.elf",
+ ":wfi_slot_1.elf",
+ ":wfi_slot_2.elf",
+ ":wfi_slot_3.elf",
]
verilator_cocotb_model(
name = "core_mini_axi_model",
+ cflags = VERILATOR_BUILD_ARGS,
hdl_toplevel = "CoreMiniAxi",
verilog_source = "//hdl/chisel/src/kelvin:CoreMiniAxi.sv",
- cflags = VERILATOR_BUILD_ARGS,
)
verilator_cocotb_model(
name = "core_mini_debug_axi_model",
+ cflags = VERILATOR_BUILD_ARGS,
hdl_toplevel = "CoreMiniDebugAxi",
verilog_source = "//hdl/chisel/src/kelvin:CoreMiniDebugAxi.sv",
- cflags = VERILATOR_BUILD_ARGS,
)
verilator_cocotb_model(
name = "rvv_core_mini_axi_model",
+ cflags = VERILATOR_BUILD_ARGS,
hdl_toplevel = "RvvCoreMiniAxi",
verilog_source = "//hdl/chisel/src/kelvin:RvvCoreMiniAxi.sv",
- cflags = VERILATOR_BUILD_ARGS,
)
-template_rule(
- verilator_cocotb_test,
- {
- "core_mini_axi_sim_cocotb": {
- "hdl_toplevel": "CoreMiniAxi",
- "model": ":core_mini_axi_model",
- "size": "enormous",
- },
- "rvv_core_mini_axi_sim_cocotb": {
- "hdl_toplevel": "RvvCoreMiniAxi",
- "model": ":rvv_core_mini_axi_model",
- "size": "enormous",
- "tags": ["manual"], # This suite takes a really long time
- },
- },
- waves = True,
- seed = "42",
- test_module = ["core_mini_axi_sim.py"],
- deps = [
- "//kelvin_test_utils:core_mini_axi_sim_interface",
- requirement("tqdm"),
- "@bazel_tools//tools/python/runfiles",
- ],
- data = COCOTB_TEST_BINARY_TARGETS,
-)
-
-verilator_cocotb_test(
- name = "core_mini_axi_debug_cocotb",
- hdl_toplevel = "CoreMiniDebugAxi",
- model = ":core_mini_debug_axi_model",
- waves = True,
- seed = "42",
- test_module = ["core_mini_axi_debug.py"],
- size = "enormous",
- deps = [
- "//kelvin_test_utils:core_mini_axi_sim_interface",
- "//kelvin_test_utils:core_mini_axi_pyocd_gdbserver",
- "@bazel_tools//tools/python/runfiles",
- ],
- data = [
- ":fptr.elf",
- ":math.elf",
- ":noop.elf",
- ":registers.elf",
- ] + COCOTB_TEST_BINARY_TARGETS,
-)
-
-TESTCASES = [
+# BEGIN_TESTCASES_FOR_core_mini_axi_sim_cocotb
+CORE_MINI_AXI_SIM_TESTCASES = [
"core_mini_axi_basic_write_read_memory",
"core_mini_axi_run_wfi_in_all_slots",
"core_mini_axi_slow_bready",
@@ -130,76 +77,193 @@
"core_mini_axi_burst_types_test",
"core_mini_axi_float_csr_test",
]
+# END_TESTCASES_FOR_core_mini_axi_sim_cocotb
-template_rule(
- vcs_cocotb_test,
- {
- "vcs_core_mini_axi_sim_cocotb": {
- "hdl_toplevel": "CoreMiniAxi",
- "verilog_sources": [
- "//hdl/chisel/src/kelvin:core_mini_axi_cc_library_verilog"
- ],
- },
- "vcs_rvv_core_mini_axi_sim_cocotb": {
- "hdl_toplevel": "RvvCoreMiniAxi",
- "verilog_sources": [
- "//hdl/chisel/src/kelvin:rvv_core_mini_axi_cc_library_verilog"
- ],
- },
- },
- size = "large",
- defines = {
- "USE_GENERIC" : "",
- },
- waves = True,
- seed = "42",
- test_module = ["core_mini_axi_sim.py"],
- testcases = TESTCASES,
- build_args = [
- "-timescale=1ns/1ps",
- "-kdb",
- "+vcs+fsdbon",
- "-debug_access+all",
- "-cm",
- "line+cond+tgl+branch+assert",
- "-cm_hier",
- "../tests/cocotb/coverage_exclude.cfg",
- ],
- test_args = [
- "+vcs+fsdbon",
- "-cm",
- "line+cond+tgl+branch+assert",
- ],
- deps = [
+# BEGIN_TESTCASES_FOR_rvv_core_mini_axi_sim_cocotb
+RVV_CORE_MINI_AXI_SIM_TESTCASES = [
+ "core_mini_axi_basic_write_read_memory",
+ "core_mini_axi_run_wfi_in_all_slots",
+ "core_mini_axi_slow_bready",
+ "core_mini_axi_write_read_memory_stress_test",
+ "core_mini_axi_master_write_alignment",
+ "core_mini_axi_finish_txn_before_halt_test",
+ "core_mini_axi_riscv_tests",
+ "core_mini_axi_riscv_dv",
+ "core_mini_axi_csr_test",
+ "core_mini_axi_exceptions_test",
+ "core_mini_axi_kelvin_isa_test",
+ "core_mini_axi_rand_instr_test",
+ "core_mini_axi_burst_types_test",
+ "core_mini_axi_float_csr_test",
+]
+# END_TESTCASES_FOR_rvv_core_mini_axi_sim_cocotb
+
+CORE_MINI_AXI_SIM_COMMON_TEST_KWARGS = {
+ "waves": True,
+ "seed": "42",
+ "test_module": ["core_mini_axi_sim.py"],
+ "deps": [
"//kelvin_test_utils:core_mini_axi_sim_interface",
requirement("tqdm"),
"@bazel_tools//tools/python/runfiles",
],
- data = COCOTB_TEST_BINARY_TARGETS + [
- ":coverage_exclude.cfg",
+ "data": COCOTB_TEST_BINARY_TARGETS,
+ "size": "enormous",
+}
+
+template_rule(
+ cocotb_test_suite,
+ {
+ "core_mini_axi_sim_cocotb": {
+ "tests_kwargs": dict(CORE_MINI_AXI_SIM_COMMON_TEST_KWARGS, hdl_toplevel = "CoreMiniAxi"),
+ "vcs_verilog_sources": ["//hdl/chisel/src/kelvin:core_mini_axi_cc_library_verilog"],
+ "verilator_model": ":core_mini_axi_model",
+ "testcases": CORE_MINI_AXI_SIM_TESTCASES,
+ "testcases_vname": "CORE_MINI_AXI_SIM_TESTCASES",
+ },
+ "rvv_core_mini_axi_sim_cocotb": {
+ "tests_kwargs": dict(CORE_MINI_AXI_SIM_COMMON_TEST_KWARGS, hdl_toplevel = "RvvCoreMiniAxi", tags = ["manual"]),
+ "vcs_verilog_sources": ["//hdl/chisel/src/kelvin:rvv_core_mini_axi_cc_library_verilog"],
+ "verilator_model": ":rvv_core_mini_axi_model",
+ "testcases": RVV_CORE_MINI_AXI_SIM_TESTCASES,
+ "testcases_vname": "RVV_CORE_MINI_AXI_SIM_TESTCASES",
+ },
+ },
+ simulators = ["verilator", "vcs"],
+ vcs_data = COCOTB_TEST_BINARY_TARGETS + [":coverage_exclude.cfg"],
+ vcs_build_args = VCS_BUILD_ARGS,
+ vcs_test_args = VCS_TEST_ARGS,
+ vcs_defines = VCS_DEFINES,
+)
+
+# BEGIN_TESTCASES_FOR_core_mini_axi_debug_cocotb
+CORE_MINI_AXI_DEBUG_TESTCASES = [
+ "core_mini_axi_debug_gdbserver",
+ "core_mini_axi_debug_dmactive",
+ "core_mini_axi_debug_probe_impl",
+ "core_mini_axi_debug_ndmreset",
+ "core_mini_axi_debug_halt_resume",
+ "core_mini_axi_debug_hartsel",
+ "core_mini_axi_debug_abstract_access_registers",
+ "core_mini_axi_debug_abstract_access_nonexistent_register",
+ "core_mini_axi_debug_single_step",
+ "core_mini_axi_debug_breakpoint",
+]
+# END_TESTCASES_FOR_core_mini_axi_debug_cocotb
+
+# BEGIN_TESTCASES_FOR_rvv_assembly_cocotb_test
+RVV_ASSEMBLY_TESTCASES = [
+ "core_mini_rvv_load",
+ "core_mini_rvv_add",
+ "core_mini_vstart_store",
+]
+# END_TESTCASES_FOR_rvv_assembly_cocotb_test
+
+# BEGIN_TESTCASES_FOR_rvv_load_store_test
+RVV_LOAD_STORE_TESTCASES = [
+ "load8_stride2_m1",
+ "load8_stride2_m1_partial",
+ "load8_stride2_mf4",
+ "load16_stride4_m1",
+ "load16_stride4_m1_partial",
+ "load16_stride4_mf2",
+ "load32_stride8_m1",
+ "load32_stride8_m1_partial",
+ "load_store8_unit_m2",
+ "load_store16_unit_m2",
+ "load_store32_unit_m2",
+ "load8_segment2_unit_m1",
+ "load16_segment2_unit_m1",
+ "load32_segment2_unit_m1",
+ "load8_segment2_unit_m2",
+ "load16_segment2_unit_m2",
+ "load32_segment2_unit_m2",
+ "load8_segment2_stride6_m1",
+ "load16_segment2_stride6_m1",
+ "load8_indexed_m1",
+ "store8_indexed_m1",
+]
+# END_TESTCASES_FOR_rvv_load_store_test
+
+# BEGIN_TESTCASES_FOR_rvv_arithmetic_cocotb_test
+RVV_ARITHMETIC_TESTCASES = [
+ "arithmetic_m1_vanilla_ops",
+ "reduction_m1_vanilla_ops",
+]
+# END_TESTCASES_FOR_rvv_arithmetic_cocotb_test
+
+cocotb_test_suite(
+ name = "core_mini_axi_debug_cocotb",
+ simulators = [
+ "verilator",
+ "vcs",
],
+ testcases = CORE_MINI_AXI_DEBUG_TESTCASES,
+ testcases_vname = "CORE_MINI_AXI_DEBUG_TESTCASES",
+ tests_kwargs = {
+ "hdl_toplevel": "CoreMiniDebugAxi",
+ "waves": True,
+ "seed": "42",
+ "test_module": ["core_mini_axi_debug.py"],
+ "size": "enormous",
+ "deps": [
+ "//kelvin_test_utils:core_mini_axi_sim_interface",
+ "//kelvin_test_utils:core_mini_axi_pyocd_gdbserver",
+ "@bazel_tools//tools/python/runfiles",
+ ],
+ "data": [
+ ":fptr.elf",
+ ":math.elf",
+ ":noop.elf",
+ ":registers.elf",
+ ],
+ },
+ vcs_data = [
+ ":fptr.elf",
+ ":math.elf",
+ ":noop.elf",
+ ":registers.elf",
+ ] + [":coverage_exclude.cfg"],
+ vcs_build_args = VCS_BUILD_ARGS,
+ vcs_test_args = VCS_TEST_ARGS,
+ vcs_defines = VCS_DEFINES,
+ vcs_verilog_sources = ["//hdl/chisel/src/kelvin:core_mini_axi_debug_cc_library_verilog"],
+ verilator_model = ":core_mini_debug_axi_model",
)
RVV_TEST_BINARY_TARGETS = [
- "//tests/cocotb/rvv:rvv_load.elf",
- "//tests/cocotb/rvv:rvv_add.elf",
- "//tests/cocotb/rvv:vstart_store.elf",
- ]
+ "//tests/cocotb/rvv:rvv_load.elf",
+ "//tests/cocotb/rvv:rvv_add.elf",
+ "//tests/cocotb/rvv:vstart_store.elf",
+]
-verilator_cocotb_test(
+cocotb_test_suite(
name = "rvv_assembly_cocotb_test",
- hdl_toplevel = "RvvCoreMiniAxi",
- model = ":rvv_core_mini_axi_model",
- waves = True,
- seed = "42",
- test_module = ["rvv_assembly_cocotb_test.py"],
- deps = [
- "//kelvin_test_utils:core_mini_axi_sim_interface",
- requirement("tqdm"),
- "@bazel_tools//tools/python/runfiles",
+ simulators = [
+ "verilator",
+ "vcs",
],
- data = RVV_TEST_BINARY_TARGETS,
- size = "large",
+ testcases = RVV_ASSEMBLY_TESTCASES,
+ testcases_vname = "RVV_ASSEMBLY_TESTCASES",
+ tests_kwargs = {
+ "hdl_toplevel": "RvvCoreMiniAxi",
+ "waves": True,
+ "seed": "42",
+ "test_module": ["rvv_assembly_cocotb_test.py"],
+ "deps": [
+ "//kelvin_test_utils:core_mini_axi_sim_interface",
+ requirement("tqdm"),
+ "@bazel_tools//tools/python/runfiles",
+ ],
+ "data": RVV_TEST_BINARY_TARGETS,
+ "size": "large",
+ },
+ vcs_data = RVV_TEST_BINARY_TARGETS + [":coverage_exclude.cfg"],
+ vcs_build_args = VCS_BUILD_ARGS,
+ vcs_test_args = VCS_TEST_ARGS,
+ vcs_defines = VCS_DEFINES,
+ vcs_verilog_sources = ["//hdl/chisel/src/kelvin:rvv_core_mini_axi_cc_library_verilog"],
+ verilator_model = ":rvv_core_mini_axi_model",
)
kelvin_v2_binary(
@@ -210,7 +274,6 @@
semihosting = True,
)
-
kelvin_v2_binary(
name = "wfi_slot_0",
srcs = [
@@ -218,7 +281,6 @@
],
)
-
kelvin_v2_binary(
name = "wfi_slot_1",
srcs = [
@@ -289,37 +351,61 @@
],
)
-verilator_cocotb_test(
+cocotb_test_suite(
name = "rvv_load_store_test",
- hdl_toplevel = "RvvCoreMiniAxi",
- model = ":rvv_core_mini_axi_model",
- waves = True,
- seed = "42",
- test_module = ["rvv_load_store_test.py"],
- deps = [
- "//kelvin_test_utils:sim_test_fixture",
- "@bazel_tools//tools/python/runfiles",
+ simulators = [
+ "verilator",
+ "vcs",
],
- data = [
- "//tests/cocotb/rvv/load_store:rvv_load_store_tests",
- ],
- size = "large",
+ testcases = RVV_LOAD_STORE_TESTCASES,
+ testcases_vname = "RVV_LOAD_STORE_TESTCASES",
+ tests_kwargs = {
+ "hdl_toplevel": "RvvCoreMiniAxi",
+ "waves": True,
+ "seed": "42",
+ "test_module": ["rvv_load_store_test.py"],
+ "deps": [
+ "//kelvin_test_utils:sim_test_fixture",
+ "@bazel_tools//tools/python/runfiles",
+ ],
+ "data": ["//tests/cocotb/rvv/load_store:rvv_load_store_tests"],
+ "size": "large",
+ },
+ vcs_data = ["//tests/cocotb/rvv/load_store:rvv_load_store_tests"] + [":coverage_exclude.cfg"],
+ vcs_build_args = VCS_BUILD_ARGS,
+ vcs_test_args = VCS_TEST_ARGS,
+ vcs_defines = VCS_DEFINES,
+ vcs_verilog_sources = ["//hdl/chisel/src/kelvin:rvv_core_mini_axi_cc_library_verilog"],
+ verilator_model = ":rvv_core_mini_axi_model",
)
-verilator_cocotb_test(
+cocotb_test_suite(
name = "rvv_arithmetic_cocotb_test",
- hdl_toplevel = "RvvCoreMiniAxi",
- model = ":rvv_core_mini_axi_model",
- waves = True,
- seed = "42",
- test_module = ["rvv_arithmetic_cocotb_test.py"],
- deps = [
- "//kelvin_test_utils:sim_test_fixture",
- "@bazel_tools//tools/python/runfiles",
- requirement("tqdm"),
+ simulators = [
+ "verilator",
+ "vcs",
],
- data = [ "//tests/cocotb/rvv/arithmetics:rvv_arith_tests" ],
- size = "large",
+ testcases = RVV_ARITHMETIC_TESTCASES,
+ testcases_vname = "RVV_ARITHMETIC_TESTCASES",
+ tests_kwargs = {
+ "hdl_toplevel": "RvvCoreMiniAxi",
+ "waves": True,
+ "seed": "42",
+ "test_module": ["rvv_arithmetic_cocotb_test.py"],
+ "deps": [
+ "//kelvin_test_utils:sim_test_fixture",
+ "@bazel_tools//tools/python/runfiles",
+ requirement("tqdm"),
+ ],
+ "data": ["//tests/cocotb/rvv/arithmetics:rvv_arith_tests"],
+ "size": "large",
+ },
+ vcs_data = ["//tests/cocotb/rvv/arithmetics:rvv_arith_tests"] + [":coverage_exclude.cfg"],
+ vcs_build_args = VCS_BUILD_ARGS,
+ vcs_test_args = VCS_TEST_ARGS,
+ vcs_defines = VCS_DEFINES,
+ vcs_verilog_sources = ["//hdl/chisel/src/kelvin:rvv_core_mini_axi_cc_library_verilog"],
+ verilator_model = ":rvv_core_mini_axi_model",
)
pkg_zip(
@@ -328,4 +414,4 @@
"//tests/cocotb/rvv/arithmetics:rvv_arith_tests",
"//tests/cocotb/rvv/load_store:rvv_load_store_tests",
],
-)
\ No newline at end of file
+)
diff --git a/tests/cocotb/build_defs.bzl b/tests/cocotb/build_defs.bzl
new file mode 100644
index 0000000..ff0816b
--- /dev/null
+++ b/tests/cocotb/build_defs.bzl
@@ -0,0 +1,49 @@
+# 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.
+
+"""Common build arguments for cocotb tests."""
+
+VERILATOR_BUILD_ARGS = [
+ "-Wno-WIDTH",
+ "-Wno-CASEINCOMPLETE",
+ "-Wno-LATCH",
+ "-Wno-SIDEEFFECT",
+ "-Wno-MULTIDRIVEN",
+ "-Wno-UNOPTFLAT",
+ # Warnings that we disable for fpnew
+ "-Wno-ASCRANGE",
+ "-Wno-WIDTHEXPAND",
+ "-Wno-WIDTHTRUNC",
+ "-Wno-UNSIGNED",
+ "-DUSE_GENERIC=\"\"",
+]
+
+VCS_BUILD_ARGS = [
+ "-timescale=1ns/1ps",
+ "-kdb",
+ "+vcs+fsdbon",
+ "-debug_access+all",
+ "-cm",
+ "line+cond+tgl+branch+assert",
+ "-cm_hier",
+ "../tests/cocotb/coverage_exclude.cfg",
+]
+
+VCS_TEST_ARGS = [
+ "+vcs+fsdbon",
+ "-cm",
+ "line+cond+tgl+branch+assert",
+]
+
+VCS_DEFINES = {"USE_GENERIC": ""}
diff --git a/tests/cocotb/tutorial/BUILD b/tests/cocotb/tutorial/BUILD
index 504b9f6..bc85393 100644
--- a/tests/cocotb/tutorial/BUILD
+++ b/tests/cocotb/tutorial/BUILD
@@ -12,38 +12,86 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-load("//rules:coco_tb.bzl", "verilator_cocotb_test")
+load("//rules:coco_tb.bzl", "cocotb_test_suite")
load("//rules:kelvin_v2.bzl", "kelvin_v2_binary")
-
-verilator_cocotb_test(
- name = "tutorial",
- waves = True,
- hdl_toplevel = "CoreMiniAxi",
- seed = "42",
- test_module = ["tutorial.py"],
- model = "//tests/cocotb:core_mini_axi_model",
- deps = [
- "//kelvin_test_utils:core_mini_axi_sim_interface",
- ],
- data = glob(["**/*.elf"]),
+load(
+ "//tests/cocotb:build_defs.bzl",
+ "VCS_BUILD_ARGS",
+ "VCS_DEFINES",
+ "VCS_TEST_ARGS",
)
-verilator_cocotb_test(
- name = "hello_world_float_core_mini_axi",
- waves = True,
- hdl_toplevel = "CoreMiniAxi",
- seed = "42",
- test_module = ["hello_world_float_core_mini_axi.py"],
- model = "//tests/cocotb:core_mini_axi_model",
- deps = [
- "//kelvin_test_utils:core_mini_axi_sim_interface",
- "@bazel_tools//tools/python/runfiles",
+# BEGIN_TESTCASES_FOR_tutorial
+TUTORIAL_TESTCASES = [
+ "core_mini_axi_tutorial",
+]
+# END_TESTCASES_FOR_tutorial
+
+# BEGIN_TESTCASES_FOR_hello_world_float_core_mini_axi
+HELLO_WORLD_TESTCASES = [
+ "core_mini_axi_tutorial",
+]
+# END_TESTCASES_FOR_hello_world_float_core_mini_axi
+
+cocotb_test_suite(
+ name = "tutorial",
+ simulators = [
+ "verilator",
+ "vcs",
],
- data = ["//examples:kelvin_v2_hello_world_add_floats.elf"],
+ testcases = TUTORIAL_TESTCASES,
+ testcases_vname = "TUTORIAL_TESTCASES",
+ tests_kwargs = {
+ "waves": True,
+ "hdl_toplevel": "CoreMiniAxi",
+ "seed": "42",
+ "test_module": ["tutorial.py"],
+ "deps": [
+ "//kelvin_test_utils:core_mini_axi_sim_interface",
+ ],
+ "data": glob(["**/*.elf"]),
+ },
+ vcs_data = [
+ "//tests/cocotb:coverage_exclude.cfg",
+ ],
+ vcs_build_args = VCS_BUILD_ARGS,
+ vcs_test_args = VCS_TEST_ARGS,
+ vcs_defines = VCS_DEFINES,
+ vcs_verilog_sources = ["//hdl/chisel/src/kelvin:core_mini_axi_cc_library_verilog"],
+ verilator_model = "//tests/cocotb:core_mini_axi_model",
+)
+
+cocotb_test_suite(
+ name = "hello_world_float_core_mini_axi",
+ simulators = [
+ "verilator",
+ "vcs",
+ ],
+ testcases = HELLO_WORLD_TESTCASES,
+ testcases_vname = "HELLO_WORLD_TESTCASES",
+ tests_kwargs = {
+ "waves": True,
+ "hdl_toplevel": "CoreMiniAxi",
+ "seed": "42",
+ "test_module": ["hello_world_float_core_mini_axi.py"],
+ "deps": [
+ "//kelvin_test_utils:core_mini_axi_sim_interface",
+ "@bazel_tools//tools/python/runfiles",
+ ],
+ "data": ["//examples:kelvin_v2_hello_world_add_floats.elf"],
+ },
+ vcs_data = [
+ "//examples:kelvin_v2_hello_world_add_floats.elf",
+ "//tests/cocotb:coverage_exclude.cfg",
+ ],
+ vcs_build_args = VCS_BUILD_ARGS,
+ vcs_test_args = VCS_TEST_ARGS,
+ vcs_defines = VCS_DEFINES,
+ vcs_verilog_sources = ["//hdl/chisel/src/kelvin:core_mini_axi_cc_library_verilog"],
+ verilator_model = "//tests/cocotb:core_mini_axi_model",
)
kelvin_v2_binary(
- name="kelvin_v2_program",
+ name = "kelvin_v2_program",
srcs = ["program.cc"],
)
-
diff --git a/utils/update_all_cocotb_tests.py b/utils/update_all_cocotb_tests.py
new file mode 100755
index 0000000..1780598
--- /dev/null
+++ b/utils/update_all_cocotb_tests.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+
+import subprocess
+import os
+import xml.etree.ElementTree as ET
+from collections import defaultdict
+
+
+def get_workspace_root():
+ return subprocess.check_output(['bazel', 'info',
+ 'workspace']).decode('utf-8').strip()
+
+
+def get_all_cocotb_test_suites():
+ xml_output = subprocess.check_output([
+ 'bazel', 'query',
+ 'kind("cocotb_test", //...) intersect attr("tags", "verilator_cocotb_test_suite", //...)',
+ '--output=xml'
+ ]).decode('utf-8').strip()
+ if not xml_output:
+ return []
+ return ET.fromstring(xml_output)
+
+
+def get_test_suite_info(suite_rule):
+ test_module = None
+ variable_name = None
+
+ for child in suite_rule:
+ if child.tag == 'list' and child.attrib.get('name') == 'test_module':
+ for label in child:
+ if label.tag == 'label':
+ test_module = label.attrib.get('value')
+ break
+ if child.tag == 'list' and child.attrib.get('name') == 'tags':
+ for string_attr in child:
+ if string_attr.tag == 'string':
+ value = string_attr.attrib.get('value', '')
+ if value.startswith("testcases_vname="):
+ variable_name = value.split("=")[1]
+ break
+ if test_module and variable_name:
+ break
+
+ return test_module, variable_name
+
+
+def main():
+ workspace_root = get_workspace_root()
+ os.chdir(workspace_root)
+
+ all_suites_xml = get_all_cocotb_test_suites()
+
+ if all_suites_xml is None:
+ print("No cocotb test suites found.")
+ return
+
+ for suite_rule in all_suites_xml.findall('rule'):
+ suite_name = suite_rule.get('name').split(':')[1]
+ print(f"Processing suite: {suite_name}")
+
+ build_file = suite_rule.get('location').split(':')[0]
+ test_module_label, variable_name = get_test_suite_info(suite_rule)
+
+ if not test_module_label or not variable_name:
+ print(
+ f"Warning: Could not extract 'test_module' or 'testcases_vname' from {suite_name}"
+ )
+ continue
+
+ # The test_module_label is a bazel label, e.g. //tests/cocotb:core_mini_axi_sim.py
+ # We need to convert it to a file path.
+ test_module_path = test_module_label.replace('//',
+ '').replace(':', '/')
+
+ test_file_path = os.path.join(workspace_root, test_module_path)
+
+ print(f"Updating testcases for {suite_name}...")
+
+ update_script_path = os.path.join(workspace_root, "utils",
+ "update_cocotb_tests.py")
+ update_command = [
+ 'python3',
+ update_script_path,
+ f'--build_file={build_file}',
+ f'--test_file={test_file_path}',
+ f'--variable_name={variable_name}',
+ f'--name={suite_name}',
+ ]
+
+ subprocess.run(update_command, check=True)
+ print(f"Successfully updated {suite_name}.")
+
+
+if __name__ == '__main__':
+ main()
diff --git a/utils/update_cocotb_tests.py b/utils/update_cocotb_tests.py
new file mode 100644
index 0000000..9267cbc
--- /dev/null
+++ b/utils/update_cocotb_tests.py
@@ -0,0 +1,59 @@
+# 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.
+
+import argparse
+import re
+
+
+def find_cocotb_tests(filename):
+ with open(filename, "r") as f:
+ source = f.read()
+ return re.findall(r"@cocotb\.test\(\)\s+async def\s+(\w+)", source)
+
+
+def update_build_file(build_file, test_file, variable_name, name):
+ test_names = find_cocotb_tests(test_file)
+ with open(build_file, "r") as f:
+ lines = f.readlines()
+
+ start_marker = f"# BEGIN_TESTCASES_FOR_{name}\n"
+ end_marker = f"# END_TESTCASES_FOR_{name}\n"
+
+ try:
+ start_index = lines.index(start_marker)
+ end_index = lines.index(end_marker)
+ except ValueError:
+ print(f"Error: Markers not found for {name} in {build_file}")
+ return
+
+ new_lines = lines[:start_index + 1]
+ new_lines.append(f'{variable_name} = [\n')
+ for name in test_names:
+ new_lines.append(f' "{name}",\n')
+ new_lines.append(']\n')
+ new_lines.extend(lines[end_index:])
+
+ with open(build_file, "w") as f:
+ f.writelines(new_lines)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--build_file", required=True)
+ parser.add_argument("--test_file", required=True)
+ parser.add_argument("--variable_name", required=True)
+ parser.add_argument("--name", required=True)
+ args = parser.parse_args()
+ update_build_file(args.build_file, args.test_file, args.variable_name,
+ args.name)