统计verilog .v文件中output 的bit数

python 复制代码
#!/usr/bin/env python3
"""Count output-port bits in a Verilog or SystemVerilog source file."""

from __future__ import annotations

import argparse
import ast
import operator
import re
import sys
from dataclasses import dataclass
from pathlib import Path


@dataclass(frozen=True)
class OutputPort:
    module: str
    name: str
    width: int
    range_text: str


_BINARY_OPERATORS = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.FloorDiv: operator.floordiv,
    ast.Div: operator.floordiv,
    ast.Mod: operator.mod,
    ast.LShift: operator.lshift,
    ast.RShift: operator.rshift,
    ast.BitOr: operator.or_,
    ast.BitAnd: operator.and_,
    ast.BitXor: operator.xor,
}
_UNARY_OPERATORS = {
    ast.UAdd: operator.pos,
    ast.USub: operator.neg,
    ast.Invert: operator.invert,
}


def strip_comments(source: str) -> str:
    """Remove Verilog comments while preserving strings and line boundaries."""
    result: list[str] = []
    index = 0
    in_string = False
    while index < len(source):
        char = source[index]
        next_char = source[index + 1] if index + 1 < len(source) else ""

        if in_string:
            result.append(char)
            if char == "\\" and next_char:
                result.append(next_char)
                index += 2
                continue
            if char == '"':
                in_string = False
            index += 1
            continue

        if char == '"':
            in_string = True
            result.append(char)
            index += 1
        elif char == "/" and next_char == "/":
            while index < len(source) and source[index] != "\n":
                index += 1
        elif char == "/" and next_char == "*":
            index += 2
            while index + 1 < len(source) and source[index : index + 2] != "*/":
                result.append("\n" if source[index] == "\n" else " ")
                index += 1
            index += 2
        else:
            result.append(char)
            index += 1
    return "".join(result)


def normalize_verilog_number(expression: str) -> str:
    """Convert common Verilog integer literals into Python integer syntax."""
    pattern = re.compile(r"(?i)(?:\d+)?'s?([bodh])([0-9a-f_xz?]+)")

    def replace(match: re.Match[str]) -> str:
        base_name = match.group(1).lower()
        digits = match.group(2).replace("_", "")
        if re.search(r"[xz?]", digits, re.I):
            raise ValueError(f"unknown digit in literal {match.group(0)!r}")
        base = {"b": 2, "o": 8, "d": 10, "h": 16}[base_name]
        return str(int(digits, base))

    return pattern.sub(replace, expression)


def evaluate_integer(expression: str, parameters: dict[str, int]) -> int:
    """Evaluate a restricted integer expression used by a packed range."""
    normalized = normalize_verilog_number(expression)
    tree = ast.parse(normalized, mode="eval")

    def evaluate(node: ast.AST) -> int:
        if isinstance(node, ast.Expression):
            return evaluate(node.body)
        if isinstance(node, ast.Constant) and isinstance(node.value, int):
            return node.value
        if isinstance(node, ast.Name) and node.id in parameters:
            return parameters[node.id]
        if isinstance(node, ast.BinOp) and type(node.op) in _BINARY_OPERATORS:
            return _BINARY_OPERATORS[type(node.op)](evaluate(node.left), evaluate(node.right))
        if isinstance(node, ast.UnaryOp) and type(node.op) in _UNARY_OPERATORS:
            return _UNARY_OPERATORS[type(node.op)](evaluate(node.operand))
        raise ValueError(f"unsupported expression {expression!r}")

    return int(evaluate(tree))


def parse_overrides(values: list[str]) -> dict[str, int]:
    parameters: dict[str, int] = {}
    for value in values:
        if "=" not in value:
            raise ValueError(f"parameter override must be NAME=VALUE: {value!r}")
        name, expression = value.split("=", 1)
        name = name.strip()
        if not re.fullmatch(r"[A-Za-z_]\w*", name):
            raise ValueError(f"invalid parameter name: {name!r}")
        parameters[name] = evaluate_integer(expression.strip(), parameters)
    return parameters


def parse_parameters(module_text: str, overrides: dict[str, int]) -> dict[str, int]:
    parameters = dict(overrides)
    declaration_pattern = re.compile(
        r"\b(?:parameter|localparam)\b(?P<body>.*?);",
        re.I | re.S,
    )
    assignment_pattern = re.compile(r"(?:\b\w+\b\s+)*\b([A-Za-z_]\w*)\s*=\s*([^,]+)")

    for declaration in declaration_pattern.finditer(module_text):
        for assignment in assignment_pattern.finditer(declaration.group("body")):
            name, expression = assignment.groups()
            if name in overrides:
                continue
            try:
                parameters[name] = evaluate_integer(expression.strip(), parameters)
            except (SyntaxError, ValueError, ZeroDivisionError):
                continue
    return parameters


def split_names(text: str) -> list[str]:
    names: list[str] = []
    for item in text.split(","):
        item = re.sub(r"\s*=.*$", "", item).strip()
        item = re.sub(r"\[[^\]]+\]\s*$", "", item).strip()
        match = re.search(r"(?:\\\S+|[A-Za-z_$][\w$]*)\s*$", item)
        if match:
            names.append(match.group(0).strip())
    return names


def range_width(range_text: str, parameters: dict[str, int]) -> int:
    if not range_text:
        return 1
    bounds = range_text[1:-1].split(":", 1)
    if len(bounds) != 2:
        raise ValueError(f"unsupported output range {range_text!r}")
    left = evaluate_integer(bounds[0].strip(), parameters)
    right = evaluate_integer(bounds[1].strip(), parameters)
    return abs(left - right) + 1


def extract_modules(source: str) -> list[tuple[str, str]]:
    modules: list[tuple[str, str]] = []
    module_pattern = re.compile(
        r"\bmodule\s+(?:automatic\s+)?([A-Za-z_$][\w$]*)\b(.*?)\bendmodule\b",
        re.I | re.S,
    )
    for match in module_pattern.finditer(source):
        modules.append((match.group(1), match.group(2)))
    return modules


def parse_outputs(source: str, overrides: dict[str, int]) -> tuple[list[OutputPort], list[str]]:
    clean_source = strip_comments(source)
    outputs: list[OutputPort] = []
    errors: list[str] = []
    output_pattern = re.compile(
        r"\boutput\b\s*"
        r"(?:wire|reg|logic|tri|tri0|tri1|wand|wor|uwire|signed|unsigned|var|supply0|supply1|\s)*"
        r"(?P<range>\[[^\]]+\])?\s*"
        r"(?P<names>[^;()]+?)\s*(?=;|,?\s*\b(?:input|output|inout)\b|\))",
        re.I | re.S,
    )

    for module_name, module_text in extract_modules(clean_source):
        parameters = parse_parameters(module_text, overrides)
        seen: set[str] = set()
        for declaration in output_pattern.finditer(module_text):
            range_text = (declaration.group("range") or "").strip()
            try:
                width = range_width(range_text, parameters)
            except (SyntaxError, ValueError, ZeroDivisionError) as error:
                names = ", ".join(split_names(declaration.group("names"))) or "<unknown>"
                errors.append(f"{module_name}: {names}: {error}")
                continue

            for name in split_names(declaration.group("names")):
                if name in seen:
                    continue
                seen.add(name)
                outputs.append(OutputPort(module_name, name, width, range_text))
    return outputs, errors


def get_options() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Count output-port bits in one Verilog/SystemVerilog file"
    )
    parser.add_argument("verilog_file", type=Path, help="input .v or .sv file")
    parser.add_argument(
        "-D",
        "--define",
        action="append",
        default=[],
        metavar="NAME=VALUE",
        help="override a parameter used in an output range; may be repeated",
    )
    parser.add_argument(
        "--total-only",
        action="store_true",
        help="print only the total output bit count",
    )
    return parser.parse_args()


def main() -> int:
    options = get_options()
    if not options.verilog_file.is_file():
        print(f"Error: file not found: {options.verilog_file}", file=sys.stderr)
        return 2

    try:
        overrides = parse_overrides(options.define)
        source = options.verilog_file.read_text(encoding="utf-8", errors="replace")
        outputs, errors = parse_outputs(source, overrides)
    except ValueError as error:
        print(f"Error: {error}", file=sys.stderr)
        return 2

    if not outputs and not errors:
        print("Error: no output declarations found", file=sys.stderr)
        return 1

    total = sum(output.width for output in outputs)
    if options.total_only:
        print(total)
    else:
        current_module = ""
        module_total = 0
        for index, output in enumerate(outputs):
            if output.module != current_module:
                if current_module:
                    print(f"  Module total: {module_total} bits\n")
                current_module = output.module
                module_total = 0
                print(f"Module: {current_module}")
                print(f"  {'Output':<40} {'Range':<20} Bits")
                print(f"  {'-' * 40} {'-' * 20} ----")
            print(f"  {output.name:<40} {(output.range_text or '[0:0]'):<20} {output.width}")
            module_total += output.width
            if index == len(outputs) - 1:
                print(f"  Module total: {module_total} bits")
        print(f"\nFile total: {total} output bits")

    for error in errors:
        print(f"Warning: unresolved output width: {error}", file=sys.stderr)
    return 1 if errors else 0


if __name__ == "__main__":
    raise SystemExit(main())

使用方法python count_verilog_outputs.py path/a.v 即可打印出path 目录下a.v的output的输出bit数

相关推荐
gwf2161 小时前
磨损均衡算法(Wear Leveling)——SSD如何让每块闪存“公平退休“?
运维·数据库·人工智能·python·嵌入式硬件·算法·智能硬件
爱吃提升2 小时前
python 分布式爬虫、爬虫合规与综合实战项目
分布式·爬虫·python
审小匠OpenCPAi2 小时前
银行流水核查怎么自动化?单边匹配、双向勾稽与图聚类异常检测的工程对比
java·前端·人工智能·python·审计
元Y亨H3 小时前
Pandas 解析 Excel 导致的内存溢出(MemoryError)
python·excel
金銀銅鐵4 小时前
[Python] 用 turtle 来绘制瑞典国旗
python
Albert Edison4 小时前
【GUI 自动化测试】Pywinauto 常见操作
自动化测试·python·pywinauto
海兰4 小时前
【高速缓存】RedisVL为文本生成嵌入向量实践指南
人工智能·python·机器学习
2601_955760074 小时前
如何用 Claude Opus 5 API 批量扩展长尾关键词和文章选题
前端·python·搜索引擎
艾斯特_4 小时前
从模型调用到可用聊天应用:会话状态与消息协议
python·ai·aigc