Skip to content
Merged
8 changes: 8 additions & 0 deletions compiler/cpp/verilog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 0 additions & 1 deletion compiler/hs/app/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,3 @@ data Options
, log_file :: Maybe String
}
deriving (Show, Data, Typeable)

6 changes: 6 additions & 0 deletions doc/mapping-to-hardware.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class Foo

<img src="./content/simple-pipeline.svg">

## 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:
Expand Down
11 changes: 11 additions & 0 deletions test/compiler/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
100 changes: 100 additions & 0 deletions test/compiler/cli/check_generated_sv_modules.py
Original file line number Diff line number Diff line change
@@ -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())
32 changes: 32 additions & 0 deletions test/compiler/cli/check_verilator_hier_blocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation.
Comment thread
teqdruid marked this conversation as resolved.
# 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 <output_dir>")
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())
14 changes: 14 additions & 0 deletions test/compiler/cli/verilator_hier_blocks.k
Original file line number Diff line number Diff line change
@@ -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;
Loading