diff --git a/compiler/cpp/options.h b/compiler/cpp/options.h index c2c6400..f3806f5 100644 --- a/compiler/cpp/options.h +++ b/compiler/cpp/options.h @@ -261,6 +261,10 @@ extern "C" // Don't output the informational header on top of generated Verilog files BOOL _noVerilogHeader; + // Emit a /*verilator hier_block*/ metacomment in the exported core + // design module to enable hierarchical Verilation + BOOL _emitVerilatorHierBlocks; + // True to enable warnings about missing [[transaction_size]] BOOL _enableTransactionSizeWarning; diff --git a/compiler/cpp/verilog.cpp b/compiler/cpp/verilog.cpp index 8a22ecd..68ba5f6 100644 --- a/compiler/cpp/verilog.cpp +++ b/compiler/cpp/verilog.cpp @@ -7118,6 +7118,17 @@ class VerilogCompiler coreModule.FinishPorts(); + // Optionally mark this module as a Verilator hierarchical block to enable + // hierarchical Verilation. The /*verilator hier_block*/ metacomment must + // appear inside the module body, after the port list. + if (GetCodeGenConfig()._emitVerilatorHierBlocks) + { + 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 70a59dd..616e2bc 100644 --- a/compiler/hs/app/Options.hs +++ b/compiler/hs/app/Options.hs @@ -122,6 +122,7 @@ data Options , template_iterations :: Int , template_passes :: Int , using :: [String] + , verilator_hier_blocks :: Bool , warnings_state :: [WarningState WarningKind] , warnings_as_errors :: Bool , work_list_size :: Int diff --git a/compiler/hs/app/Options/CmdArgs.hs b/compiler/hs/app/Options/CmdArgs.hs index ff92b77..e9633a7 100644 --- a/compiler/hs/app/Options/CmdArgs.hs +++ b/compiler/hs/app/Options/CmdArgs.hs @@ -81,6 +81,7 @@ compile = Compile , no_fifo_stutter = def &= groupname "Code generation" &= help "Disable fifos stutter" , no_debug_view = def &= groupname "Code generation" &= help "Disables debug views" , no_verilog_header = def &= groupname "Code generation" &= help "Don't output the informational header on top of generated Verilog files" + , verilator_hier_blocks = def &= groupname "Code generation" &= help "Emit a /*verilator hier_block*/ metacomment in the exported core design module to enable hierarchical Verilation" &= name "verilator-hier-blocks" &= explicit , code_coverage = def &= groupname "Code generation" &= help "Enable generation of code coverage using SystemVerilog coverpoints" , code_coverage_mux_threshold = 0 &= groupname "Code generation" &= help "Only generate code coverage code for muxes with this number of cases; the default value of 0 means to generate for all muxes" , optimize = 2 &= groupname "Code generation" &= help "Optimize generated code" &= typ "LEVEL" &= name "O" diff --git a/compiler/hs/app/Options/FFI.hsc b/compiler/hs/app/Options/FFI.hsc index fe4e28d..59ba398 100644 --- a/compiler/hs/app/Options/FFI.hsc +++ b/compiler/hs/app/Options/FFI.hsc @@ -98,6 +98,7 @@ instance Storable Options where #{poke CodeGenOptions, _fifoWriteDelay } ptrCodegen fifo_write_delay #{poke CodeGenOptions, _fifoMergeDistance } ptrCodegen fifo_merge_distance #{poke CodeGenOptions, _noVerilogHeader } ptrCodegen no_verilog_header + #{poke CodeGenOptions, _emitVerilatorHierBlocks } ptrCodegen verilator_hier_blocks #{poke CodeGenOptions, _optimize } ptrCodegen optimize #{poke CodeGenOptions, _logicRegisterRatio } ptrCodegen register_ratio #{poke CodeGenOptions, _maxLogicRegisterRatio } ptrCodegen max_register_ratio diff --git a/test/compiler/cli/CMakeLists.txt b/test/compiler/cli/CMakeLists.txt index 858c4fc..fddc076 100644 --- a/test/compiler/cli/CMakeLists.txt +++ b/test/compiler/cli/CMakeLists.txt @@ -104,3 +104,15 @@ add_cli_test(skip_circt_lowering TEST "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 + --verilator-hier-blocks + --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" +) diff --git a/test/compiler/cli/__pycache__/check_verilator_hier_blocks.cpython-312.pyc b/test/compiler/cli/__pycache__/check_verilator_hier_blocks.cpython-312.pyc new file mode 100644 index 0000000..1b87a08 Binary files /dev/null and b/test/compiler/cli/__pycache__/check_verilator_hier_blocks.cpython-312.pyc differ 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 0000000..1b61cc6 --- /dev/null +++ b/test/compiler/cli/check_verilator_hier_blocks.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +""" +Verify the output of a `--verilator-hier-blocks` compile. + +Contract of the flag: the exported design (core) module should carry a +`/*verilator hier_block*/` metacomment, which enables hierarchical +Verilation. Verilator requires this metacomment to appear inside the +module body (after the `module name(...);` port list). The metacomment is +*selective*: it is only emitted on the core design module, not on the +non-exported helper modules that the compiler generates alongside it (the +ESI wrapper, the per-basic-block modules, etc.). + +Checks: + 1. At least one .sv file exists. + 2. The `/*verilator hier_block*/` metacomment appears inside at least one + generated module body (i.e. after that module's `module name(...);` + declaration, as Verilator requires). + 3. At least one other generated module exists that does *not* contain the + metacomment, proving the annotation is confined to the exported design + module and is not blanket-applied to non-exported modules. + +Exits non-zero on failure. +""" +import argparse +import re +import sys +from pathlib import Path + +HIER_BLOCK = '/*verilator hier_block*/' + +# Matches a SystemVerilog module declaration, e.g. `module Foo (`, capturing +# the module name. +MODULE_DECL = re.compile(r'(?:^|\s)module\s+(\w+)', re.MULTILINE) + + +def collect_modules(sv_files): + """Return a list of (sv_name, module_name, body) for every module. + + Modules do not nest in SystemVerilog, so each module body is the text + spanning from its `module name` declaration up to the next module + declaration (or end of file). Slicing on declaration boundaries avoids + relying on an `endmodule` token, which could otherwise appear inside a + comment or string and truncate the body prematurely. + """ + 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 has_hier_block_after_port_list(module_body): + """Return True if HIER_BLOCK appears after the declaration port list.""" + port_list_end = module_body.find(');') + if port_list_end == -1: + return False + return module_body.find(HIER_BLOCK, port_list_end + 2) != -1 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('output_dir', help='Directory containing compiler outputs') + 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 + + sv_files = sorted(output_dir.glob('*.sv')) + if not sv_files: + print("--verilator-hier-blocks should produce a .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 + + with_meta = [m for m in modules if HIER_BLOCK in m[2]] + with_meta_after_ports = [m for m in modules if has_hier_block_after_port_list(m[2])] + without_meta = [m for m in modules if HIER_BLOCK not in m[2]] + module_names = ', '.join(m[1] for m in modules) + + # Check 2: the metacomment must appear inside at least one module body. + if not with_meta: + print( + f"--verilator-hier-blocks must emit {HIER_BLOCK!r} into a generated " + f"module, but none of these modules contain it: {module_names}." + ) + return 1 + + if not with_meta_after_ports: + print( + f"--verilator-hier-blocks must emit {HIER_BLOCK!r} after a generated " + "module's port list (after `);`), but no module matches this placement " + f"requirement: {module_names}." + ) + return 1 + + # Check 3: the metacomment must be selective. The compiler emits several + # non-exported helper modules (ESI wrapper, per-basic-block modules); these + # must not carry the metacomment. + if not without_meta: + print( + f"--verilator-hier-blocks should only annotate the exported design " + f"module, but every generated module contains {HIER_BLOCK!r}: " + f"{module_names}." + ) + return 1 + + return 0 + + +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 0000000..b109fe4 --- /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-blocks CLI test. +class VerilatorHierBlock +{ +public: + uint32 TimesTwo(uint32 x) + { + return x + x; + } +} + +export VerilatorHierBlock;