Skip to content

Commit

Permalink
Merge branch 'main' into cfg_add_maintainer
Browse files Browse the repository at this point in the history
  • Loading branch information
miceforrat committed Jan 20, 2025
2 parents d0f8d6d + 0867b97 commit 44aeada
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/workflows/self-hosted-ci-pr-checker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:

- name: Check changed files
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git fetch origin ${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }}
git diff --name-only origin/${{ github.event.pull_request.base.ref }} ${{ github.event.pull_request.head.sha }} > changed_files.txt
cat changed_files.txt
Expand Down
105 changes: 105 additions & 0 deletions comm/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,108 @@ def module_name_with(names, prefix=None):
elif isinstance(names, list):
return [mname + "." + n for n in names]
raise ValueError("Invalid names type")


def get_all_rtl_files(top_module, cfg):
"""
Returns the file paths of all modules that the `top_module` depends on,
with the path of `top_module` as the first element in the list.
This function assumes that there is **only one module** in the file and
that the **file name matches the module name**.
"""
import re
from glob import iglob
from collections import OrderedDict

some_verilog_keywords = {
'for', 'real', 'initial', 'input', 'endcase', 'typedef', 'primitive', 'always_comb', 'always_latch', 'negedge',
'repeat', 'while', 'endfunction', 'int', 'output', 'wire', 'logic', 'reg', 'assign', 'function', 'case',
'always_ff', 'if', 'posedge', 'table', 'end', 'task', 'forever', 'enum', 'endtask', 'module', 'localparam',
'timescale', 'endprimitive', 'else', 'endtable', 'always', 'parameter', 'time', 'endmodule', 'begin',
"and", "or", "not", "xor"
}

module_pattern = re.compile(r"\bmodule\s+(\w+)\b")
instance_pattern = re.compile(r"\b(\w+)\s+(?!module)(\w+)\s*\(")
module_path_map = OrderedDict()

def resolve_verilog_file(path):
module_set = set()
instance_set = set()

def remove_inline_comments(s):
# Remove the line comment first
s = re.sub(r"//.*$", "", s)
# Then remove the block comment
return re.sub(r'/\*.*?\*/', "", s)

def parse_line(line_text: str) -> None:
_line = remove_inline_comments(line_text)

# Extract names of declared modules
module_matches = module_pattern.finditer(_line)
for match in module_matches:
_name = match.group(1)
module_set.add(_name)

# Extract names of instanced modules
instance_matches = instance_pattern.finditer(_line)
for match in instance_matches:
_name = match.group(1)
if _name not in some_verilog_keywords:
instance_set.add(_name)

# Code begin
block_comment_depth = 0
pending_line = ''

with open(path, "r") as file:
while True:
chunk = file.read(32768)
if not chunk:
break

lines = ("".join((pending_line, chunk))).split("\n")
if lines:
pending_line = lines.pop()

for line in lines:
# for block comment
start_pos = line.find("/*")
end_pos = line.find("*/")
while start_pos != -1 or end_pos != -1:
# if '/*' appears before '*/', increase depth
if start_pos != -1 and (end_pos == -1 or start_pos < end_pos):
block_comment_depth += 1
line = " ".join((line[:start_pos], line[start_pos + 2:]))
# if '*/' appears before '/*', decrease depth
elif end_pos != -1:
block_comment_depth -= 1
line = " ".join((line[:end_pos], line[end_pos + 2:]))
start_pos = line.find("/*")
end_pos = line.find("*/")

# skip if content of current line is in block comment
if block_comment_depth > 0:
continue
parse_line(line)
if pending_line:
parse_line(pending_line)
return module_set, instance_set

def get_rtl_helper(top_module_name) -> None:
from comm import get_rtl_dir
# Walk through the rtl dir
rtl_dir = os.path.join(str(get_rtl_dir(cfg=cfg)), f"**/{top_module_name}.*v")
for path in iglob(rtl_dir, recursive=True):
module_set, inst_set = resolve_verilog_file(path)
for _name in module_set:
module_path_map[_name] = path
module_set.clear()
for _name in inst_set:
if _name not in module_path_map:
get_rtl_helper(_name)

get_rtl_helper(top_module)
return list(module_path_map.values())
25 changes: 25 additions & 0 deletions documents/content/zh-cn/docs/95_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,28 @@ print(comm.module_name_with(["X", "Y"], ,"../../x"))
# out
["a.b.c.x.X", "a.b.c.x.Y"]
```


#### `get_all_rtl_files(top_module, cfg)`

获取名称为 `top_module` 的模块所依赖的所有 RTL 文件(`.v``.sv`)的列表,并确保列表的第一个元素是 `top_module` 所在文件的绝对路径。所有 RTL 文件均位于 `UnityChipForXiangShan/rtl/rtl` 目录下。

- 输入:
- `top_module`:模块名称,类型为 `str`
- `cfg`:配置信息,类型为 `CfgObject`

- 输出:
- 返回一个包含字符串的列表,列表中的每个字符串为模块依赖的 RTL 文件的绝对路径。列表的第一个元素为 `top_module` 所在文件的路径。

假设 `top_module``"ALU"`,且其依赖的 RTL 文件包括 `ALU.sv``adder.v``multiplier.v`
```python
paths = get_all_rtl_files("ALU", cfg)

"""
paths可能的内容:
[
"/path/to/UnityChipForXiangShan/rtl/rtl/ALU.sv",
"/path/to/UnityChipForXiangShan/rtl/rtl/adder.v",
"/path/to/UnityChipForXiangShan/rtl/rtl/multiplier.v"
]
"""

0 comments on commit 44aeada

Please sign in to comment.