Compare commits

...

7 Commits

Author SHA1 Message Date
Frank OU 0867b97b00 Update self-hosted-ci-pr-checker.yaml 2025-01-20 11:05:32 +08:00
Frank OU bde2c7a01d Update self-hosted-ci-pr-checker.yaml 2025-01-20 11:05:32 +08:00
FrankOu 91cede469d Add new function `get_all_rtl_files` in `functions.py` 2025-01-20 11:05:32 +08:00
Frank OU 3754c85810 Update self-hosted-ci-pr-checker.yaml
Add `make rtl` to donwload rtl in `Run test in the changed ut_* directories` step.
2025-01-18 16:44:04 +08:00
Frank OU cddb17b008 Fix: Fail to pass `Check changed files` step of PR-CHECK workflow
Add "safe directory exception" to solve it.
2025-01-17 17:24:41 +08:00
miceforrat deb6bec3a6
Create __init__.py (infact add dut) 2025-01-17 14:00:23 +08:00
miceforrat a1bb767c7f
Create README.md 2025-01-17 13:58:35 +08:00
5 changed files with 133 additions and 0 deletions

View File

@ -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

View File

@ -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())

View File

@ -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"
]
"""

1
dut/__init__.py Normal file
View File

@ -0,0 +1 @@

1
rtl/README.md Normal file
View File

@ -0,0 +1 @@
#### The RTL placeholder. Please do not add new files to this directory.