diff --git a/compiler/cpp/verilog.cpp b/compiler/cpp/verilog.cpp index 510f78a4..d5f7a9b6 100644 --- a/compiler/cpp/verilog.cpp +++ b/compiler/cpp/verilog.cpp @@ -7134,6 +7134,14 @@ class VerilogCompiler coreModule.FinishPorts(); + // Mark this module as a Verilator hierarchical block. The + // /*verilator hier_block*/ metacomment must appear inside the module + // body, after the port list. + coreModule.AddVerbatimOp(GetUnknownLocation(), [&](VerbatimWriter &writer) + { + writer << "/*verilator hier_block*/"; + }); + DeclareDebugVariables(); DeclareStringTable(); diff --git a/compiler/hs/app/Options.hs b/compiler/hs/app/Options.hs index 4ac42373..5d335a6f 100644 --- a/compiler/hs/app/Options.hs +++ b/compiler/hs/app/Options.hs @@ -145,4 +145,3 @@ data Options , log_file :: Maybe String } deriving (Show, Data, Typeable) - diff --git a/doc/mapping-to-hardware.md b/doc/mapping-to-hardware.md index f6e5dc45..56e1c065 100644 --- a/doc/mapping-to-hardware.md +++ b/doc/mapping-to-hardware.md @@ -34,6 +34,12 @@ class Foo +## Verilator hierarchical blocks +The generated core design module for each `export` class includes the +`/*verilator hier_block*/` metacomment immediately after the module port list. +This enables [hierarchical Verilation](https://veripool.org/guide/latest/verilating.html#hierarchical-verilation) +for Verilator users while leaving helper modules (such as wrappers) unannotated. + ## Threads Threads in Kanagawa are runtime constructs. The source does not specify the number of threads that will be created. A thread is defined by: diff --git a/test/compiler/cli/CMakeLists.txt b/test/compiler/cli/CMakeLists.txt index 1ac3f2a7..7ab328f7 100644 --- a/test/compiler/cli/CMakeLists.txt +++ b/test/compiler/cli/CMakeLists.txt @@ -105,6 +105,17 @@ add_cli_test(skip_circt_lowering "python3 ${CMAKE_CURRENT_SOURCE_DIR}/check_skip_circt_lowering.py ${CMAKE_CURRENT_BINARY_DIR}/skip_circt_lowering" ) +add_cli_test(verilator_hier_blocks + SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/verilator_hier_blocks.k + OPTIONS + --backend=sv + --base-library=${CMAKE_SOURCE_DIR}/library/mini-base.k + --import-dir=${CMAKE_SOURCE_DIR}/library + --place-iterations=1 + TEST + "python3 ${CMAKE_CURRENT_SOURCE_DIR}/check_verilator_hier_blocks.py ${CMAKE_CURRENT_BINARY_DIR}/verilator_hier_blocks" +) + # `list-deps` resolves imports without running the frontend or codegen, so it # does not fit add_cli_test's compile-and-inspect shape. add_golden_test(cli.list_deps diff --git a/test/compiler/cli/check_generated_sv_modules.py b/test/compiler/cli/check_generated_sv_modules.py new file mode 100644 index 00000000..d7927780 --- /dev/null +++ b/test/compiler/cli/check_generated_sv_modules.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +""" +Generic checks for generated SystemVerilog modules. + +This script parses all `module` declarations in `*.sv` files under an output +directory and applies generic pattern-based assertions. +""" +import argparse +import re +import sys +from pathlib import Path + +MODULE_DECL = re.compile(r'(?:^|\s)module\s+(\w+)', re.MULTILINE) + + +def collect_modules(sv_files): + modules = [] + for sv in sv_files: + text = sv.read_text() + decls = list(MODULE_DECL.finditer(text)) + for i, decl in enumerate(decls): + end = decls[i + 1].start() if i + 1 < len(decls) else len(text) + modules.append((sv.name, decl.group(1), text[decl.start():end])) + return modules + + +def check_modules( + output_dir, + expect_any_regex=(), + expect_any_regex_after_port_list=(), + expect_any_without_regex=(), +): + sv_files = sorted(output_dir.glob('*.sv')) + if not sv_files: + print("Expected at least one generated .sv file, but none was found.") + return 1 + + modules = collect_modules(sv_files) + if not modules: + names = ', '.join(s.name for s in sv_files) + print(f"No SystemVerilog modules found in {names}.") + return 1 + + module_names = ', '.join(m[1] for m in modules) + + for pattern in expect_any_regex: + regex = re.compile(pattern, re.MULTILINE) + if not any(regex.search(m[2]) for m in modules): + print(f"Expected at least one module to match regex {pattern!r}: {module_names}.") + return 1 + + for pattern in expect_any_regex_after_port_list: + regex = re.compile(pattern, re.MULTILINE) + matched = False + for _, _, body in modules: + port_list_end = body.find(');') + if port_list_end != -1 and regex.search(body[port_list_end + 2:]): + matched = True + break + if not matched: + print( + f"Expected at least one module to match regex {pattern!r} after " + f"the port list (`);`): {module_names}." + ) + return 1 + + for pattern in expect_any_without_regex: + regex = re.compile(pattern, re.MULTILINE) + if not any(not regex.search(m[2]) for m in modules): + print(f"Expected at least one module not to match regex {pattern!r}: {module_names}.") + return 1 + + return 0 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('output_dir', help='Directory containing compiler outputs') + parser.add_argument('--expect-any-regex', action='append', default=[]) + parser.add_argument('--expect-any-regex-after-port-list', action='append', default=[]) + parser.add_argument('--expect-any-without-regex', action='append', default=[]) + args = parser.parse_args() + + output_dir = Path(args.output_dir) + if not output_dir.is_dir(): + print(f"Output directory does not exist: {output_dir}") + return 1 + + return check_modules( + output_dir, + expect_any_regex=args.expect_any_regex, + expect_any_regex_after_port_list=args.expect_any_regex_after_port_list, + expect_any_without_regex=args.expect_any_without_regex, + ) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/test/compiler/cli/check_verilator_hier_blocks.py b/test/compiler/cli/check_verilator_hier_blocks.py new file mode 100644 index 00000000..4f5228d9 --- /dev/null +++ b/test/compiler/cli/check_verilator_hier_blocks.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""Verify hier_block annotations in generated SystemVerilog output.""" +import sys +from pathlib import Path + +from check_generated_sv_modules import check_modules + +HIER_BLOCK_REGEX = r'/\*verilator hier_block\*/' + + +def main(): + if len(sys.argv) != 2: + print("Usage: check_verilator_hier_blocks.py ") + return 1 + + output_dir = Path(sys.argv[1]) + if not output_dir.is_dir(): + print(f"Output directory does not exist: {output_dir}") + return 1 + + return check_modules( + output_dir, + expect_any_regex=[HIER_BLOCK_REGEX], + expect_any_regex_after_port_list=[HIER_BLOCK_REGEX], + expect_any_without_regex=[HIER_BLOCK_REGEX], + ) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/test/compiler/cli/verilator_hier_blocks.k b/test/compiler/cli/verilator_hier_blocks.k new file mode 100644 index 00000000..d43698c0 --- /dev/null +++ b/test/compiler/cli/verilator_hier_blocks.k @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Minimal program used by the Verilator hier_block CLI test. +class VerilatorHierBlock +{ +public: + uint32 TimesTwo(uint32 x) + { + return x + x; + } +} + +export VerilatorHierBlock;