diff --git a/.bazelrc b/.bazelrc index 36d2d26e..5a28978e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -3,6 +3,7 @@ build --copt=-Wno-unknown-pragmas build --copt=-Wno-unused-label build --copt=-Werror build --incompatible_autoload_externally=+@rules_python,+@rules_shell +build --incompatible_default_to_explicit_init_py # Don't overwrite __init__.py files build --incompatible_strict_action_env build:asan --compilation_mode=dbg diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 8faac7f8..cc3df29e 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -352,7 +352,7 @@ "bzlTransitiveDigest": "DAmOIymKReaXHAeY4UCKtaDNew0xtBYdU1clXDthrrQ=", "usagesDigest": "IJbT85GnJlUmNdpJqq0oX1PY5+jehx34bxm4QlMJ2CU=", "recordedFileInputs": { - "@@//tapa/requirements_lock.txt": "3d4788b9399e46f42987b56e99e0dd8c4c305bc3d7c972fb6cddcc3a82b5db31", + "@@//tapa/requirements_lock.txt": "ee5e4f95589b97047d35c539ddc6dcd71aa45135a80548a0ed4c832969854a45", "@@protobuf+//python/requirements.txt": "983be60d3cec4b319dcab6d48aeb3f5b2f7c3350f26b3a9e97486c37967c73c5", "@@rules_fuzzing+//fuzzing/requirements.txt": "ab04664be026b632a0d2a2446c4f65982b7654f5b6851d2f9d399a19b7242a5b", "@@rules_python+//tools/publish/requirements_darwin.txt": "2994136eab7e57b083c3de76faf46f70fad130bc8e7360a7fed2b288b69e79dc", @@ -2880,6 +2880,15 @@ "requirement": "humanfriendly==10.0 --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 --hash=sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc" } }, + "tapa_deps_311_iniconfig": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@tapa_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_11_10_host//:python", + "repo": "tapa_deps_311", + "requirement": "iniconfig==2.0.0 --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + } + }, "tapa_deps_311_jinja2": { "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", "attributes": { @@ -2916,6 +2925,24 @@ "requirement": "ordered-set==4.1.0 --hash=sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562 --hash=sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8" } }, + "tapa_deps_311_packaging": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@tapa_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_11_10_host//:python", + "repo": "tapa_deps_311", + "requirement": "packaging==24.2 --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + } + }, + "tapa_deps_311_pluggy": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@tapa_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_11_10_host//:python", + "repo": "tapa_deps_311", + "requirement": "pluggy==1.5.0 --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" + } + }, "tapa_deps_311_ply": { "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", "attributes": { @@ -2934,6 +2961,15 @@ "requirement": "psutil==6.1.1 --hash=sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca --hash=sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377 --hash=sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468 --hash=sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3 --hash=sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603 --hash=sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac --hash=sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303 --hash=sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4 --hash=sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160 --hash=sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8 --hash=sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003 --hash=sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030 --hash=sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777 --hash=sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5 --hash=sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53 --hash=sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649 --hash=sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8" } }, + "tapa_deps_311_pytest": { + "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", + "attributes": { + "dep_template": "@tapa_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python++python+python_3_11_10_host//:python", + "repo": "tapa_deps_311", + "requirement": "pytest==8.3.4 --hash=sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6 --hash=sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761" + } + }, "tapa_deps_311_pyverilog": { "repoRuleId": "@@rules_python+//python/private/pypi:whl_library.bzl%whl_library", "attributes": { @@ -3077,12 +3113,16 @@ "click": "{\"tapa_deps_311_click\":[{\"version\":\"3.11\"}]}", "coloredlogs": "{\"tapa_deps_311_coloredlogs\":[{\"version\":\"3.11\"}]}", "humanfriendly": "{\"tapa_deps_311_humanfriendly\":[{\"version\":\"3.11\"}]}", + "iniconfig": "{\"tapa_deps_311_iniconfig\":[{\"version\":\"3.11\"}]}", "jinja2": "{\"tapa_deps_311_jinja2\":[{\"version\":\"3.11\"}]}", "markupsafe": "{\"tapa_deps_311_markupsafe\":[{\"version\":\"3.11\"}]}", "nuitka": "{\"tapa_deps_311_nuitka\":[{\"version\":\"3.11\"}]}", "ordered_set": "{\"tapa_deps_311_ordered_set\":[{\"version\":\"3.11\"}]}", + "packaging": "{\"tapa_deps_311_packaging\":[{\"version\":\"3.11\"}]}", + "pluggy": "{\"tapa_deps_311_pluggy\":[{\"version\":\"3.11\"}]}", "ply": "{\"tapa_deps_311_ply\":[{\"version\":\"3.11\"}]}", "psutil": "{\"tapa_deps_311_psutil\":[{\"version\":\"3.11\"}]}", + "pytest": "{\"tapa_deps_311_pytest\":[{\"version\":\"3.11\"}]}", "pyverilog": "{\"tapa_deps_311_pyverilog\":[{\"version\":\"3.11\"}]}", "pyyaml": "{\"tapa_deps_311_pyyaml\":[{\"version\":\"3.11\"}]}", "toposort": "{\"tapa_deps_311_toposort\":[{\"version\":\"3.11\"}]}", @@ -3092,12 +3132,16 @@ "click", "coloredlogs", "humanfriendly", + "iniconfig", "jinja2", "markupsafe", "nuitka", "ordered_set", + "packaging", + "pluggy", "ply", "psutil", + "pytest", "pyverilog", "pyyaml", "toposort", diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel index ccb1be7a..1f36b355 100644 --- a/bazel/BUILD.bazel +++ b/bazel/BUILD.bazel @@ -27,7 +27,10 @@ string_flag( ], ) -exports_files(["nuitka_wrapper.py"]) +exports_files([ + "nuitka_wrapper.py", + "pytest_wrapper.py", +]) xilinx_wrapper( name = "v++", diff --git a/bazel/pytest_rules.bzl b/bazel/pytest_rules.bzl new file mode 100644 index 00000000..dd117905 --- /dev/null +++ b/bazel/pytest_rules.bzl @@ -0,0 +1,18 @@ +"""Custom rule to test Python modules with pytest.""" + +# Copyright (c) 2025 RapidStream Design Automation, Inc. and contributors. +# All rights reserved. The contributor(s) of this file has/have agreed to the +# RapidStream Contributor License Agreement. + +load("@rules_python//python:defs.bzl", _py_test = "py_test") +load("@tapa_deps//:requirements.bzl", "requirement") + +def py_test(name, srcs, deps = [], args = [], **kwargs): + _py_test( + name = name, + main = "//bazel:pytest_wrapper.py", + srcs = srcs + ["//bazel:pytest_wrapper.py"], + deps = deps + [requirement("pytest")], + args = args + ["$(location %s)" % x for x in srcs], + **kwargs + ) diff --git a/bazel/pytest_wrapper.py b/bazel/pytest_wrapper.py new file mode 100644 index 00000000..23362427 --- /dev/null +++ b/bazel/pytest_wrapper.py @@ -0,0 +1,12 @@ +import sys + +import pytest + +__copyright__ = """ +Copyright (c) 2025 RapidStream Design Automation, Inc. and contributors. +All rights reserved. The contributor(s) of this file has/have agreed to the +RapidStream Contributor License Agreement. +""" + +if __name__ == "__main__": + sys.exit(pytest.main(sys.argv[1:])) diff --git a/tapa/BUILD.bazel b/tapa/BUILD.bazel index e63e6a59..34832ea3 100644 --- a/tapa/BUILD.bazel +++ b/tapa/BUILD.bazel @@ -94,6 +94,7 @@ py_library( ":util", "//tapa/backend", "//tapa/verilog", + "//tapa/verilog/xilinx:module", requirement("psutil"), requirement("pyverilog"), requirement("pyyaml"), diff --git a/tapa/requirements.in b/tapa/requirements.in index f8bcfcf6..1cd7f534 100644 --- a/tapa/requirements.in +++ b/tapa/requirements.in @@ -3,6 +3,7 @@ coloredlogs jinja2 nuitka psutil +pytest pyverilog pyyaml toposort diff --git a/tapa/requirements_lock.txt b/tapa/requirements_lock.txt index 71e39c61..23ffcf2c 100644 --- a/tapa/requirements_lock.txt +++ b/tapa/requirements_lock.txt @@ -16,6 +16,10 @@ humanfriendly==10.0 \ --hash=sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477 \ --hash=sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc # via coloredlogs +iniconfig==2.0.0 \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 + # via pytest jinja2==3.1.5 \ --hash=sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb \ --hash=sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb @@ -92,6 +96,14 @@ ordered-set==4.1.0 \ --hash=sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562 \ --hash=sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8 # via nuitka +packaging==24.2 \ + --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ + --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f + # via pytest +pluggy==1.5.0 \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 + # via pytest ply==3.11 \ --hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \ --hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce @@ -115,6 +127,10 @@ psutil==6.1.1 \ --hash=sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649 \ --hash=sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8 # via -r tapa/requirements.in +pytest==8.3.4 \ + --hash=sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6 \ + --hash=sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761 + # via -r tapa/requirements.in pyverilog==1.3.0 \ --hash=sha256:59d93e9004ebe9e713e2fd6a9784a09e2d6b3c098091fd367795eb20329ae4a8 # via -r tapa/requirements.in diff --git a/tapa/verilog/BUILD.bazel b/tapa/verilog/BUILD.bazel index 28739997..13f36a6b 100644 --- a/tapa/verilog/BUILD.bazel +++ b/tapa/verilog/BUILD.bazel @@ -11,6 +11,7 @@ py_library( srcs = glob(["*.py"]), visibility = ["//tapa:__subpackages__"], deps = [ + "//tapa:util", "//tapa/backend", "//tapa/verilog/xilinx", requirement("jinja2"), diff --git a/tapa/verilog/xilinx/BUILD.bazel b/tapa/verilog/xilinx/BUILD.bazel index 9e21aa14..0fe64ca3 100644 --- a/tapa/verilog/xilinx/BUILD.bazel +++ b/tapa/verilog/xilinx/BUILD.bazel @@ -5,6 +5,7 @@ # RapidStream Contributor License Agreement. load("@tapa_deps//:requirements.bzl", "requirement") +load("//bazel:pytest_rules.bzl", "py_test") package( default_visibility = ["//tapa:__subpackages__"], @@ -14,6 +15,10 @@ py_library( name = "xilinx", srcs = glob( ["*.py"], + exclude = [ + "module.py", + "*_test.py", + ], ), deps = [ "//tapa/backend", @@ -21,3 +26,22 @@ py_library( requirement("pyverilog"), ], ) + +py_library( + name = "module", + srcs = ["module.py"], + deps = [ + "//tapa/backend", + "//tapa/verilog", + requirement("jinja2"), + requirement("pyverilog"), + ], +) + +py_test( + name = "module_test", + srcs = ["module_test.py"], + deps = [ + ":module", + ], +) diff --git a/tapa/verilog/xilinx/module_test.py b/tapa/verilog/xilinx/module_test.py new file mode 100644 index 00000000..07a95bed --- /dev/null +++ b/tapa/verilog/xilinx/module_test.py @@ -0,0 +1,28 @@ +"""Unit tests for tapa.verilog.xilinx.module.""" + +__copyright__ = """ +Copyright (c) 2025 RapidStream Design Automation, Inc. and contributors. +All rights reserved. The contributor(s) of this file has/have agreed to the +RapidStream Contributor License Agreement. +""" + +import pytest + +from tapa.verilog.xilinx.module import Module + + +def test_invalid_module() -> None: + with pytest.raises(ValueError, match="`files` and `name` cannot both be empty"): + Module() + + +def test_empty_module() -> None: + """An empty module can be constructed from a name. + + This is used to create placeholders before Verilog is parsed, and to create + skeleton FSM modules. + """ + module = Module(name="foo") + + assert module.name == "foo" + assert not module.ports