4. 模块实例化自动生成

任务目标

给定一个Verilog模块的端口声明,自动生成该模块的实例化模板代码,包含端口连接与参数传递占位符,减少手工编写实例化代码的重复工作。

实现方案

使用Python脚本解析Verilog模块定义,提取模块名、参数列表与端口列表(含方向与位宽),然后按标准模板生成实例化代码。输入为包含模块定义的文本文件,输出为可直接粘贴到设计中的实例化模板。

完整代码

import re
import sys

def parse_module(verilog_text):
    """提取模块名、参数与端口信息"""
    # 匹配 module name #(params) (ports);
    mod_match = re.search(r'\bmodule\s+(\w+)\s*(?:#\s*\((.*?)\))?\s*\((.*?)\)\s*;', 
                          verilog_text, re.DOTALL)
    if not mod_match:
        raise ValueError("未找到有效的module声明")
    
    mod_name = mod_match.group(1)
    param_str = mod_match.group(2) or ''
    port_str = mod_match.group(3)
    
    # 解析参数
    params = []
    if param_str.strip():
        for p in param_str.split(','):
            p = p.strip()
            if not p:
                continue
            # 支持 parameter WIDTH=8 或 parameter WIDTH = 8
            m = re.match(r'(?:parameter\s+)?(\w+)\s*=\s*([^,]+)', p)
            if m:
                params.append((m.group(1), m.group(2).strip()))
    
    # 解析端口
    ports = []
    # 按顶层逗号分割(忽略括号内的逗号)
    depth = 0
    current = ''
    for ch in port_str:
        if ch == '(':
            depth += 1
        elif ch == ')':
            depth -= 1
        if ch == ',' and depth == 0:
            ports.append(current.strip())
            current = ''
        else:
            current += ch
    if current.strip():
        ports.append(current.strip())
    
    parsed_ports = []
    for port in ports:
        if not port:
            continue
        # 匹配方向与名称,如 input wire [7:0] data_in
        m = re.match(r'\s*(input|output|inout)\s+(?:wire|reg)?\s*(?:\[[^\]]+\]\s*)?(\w+)\s*$', port)
        if m:
            direction = m.group(1)
            name = m.group(2)
            # 提取位宽
            bw = re.search(r'\[([^\]]+)\]', port)
            width = bw.group(1) if bw else ''
            parsed_ports.append({'name': name, 'direction': direction, 'width': width})
        else:
            # 兼容ANSI风格中的简单声明
            m2 = re.match(r'\s*(input|output|inout)\s+(\w+)\s*$', port)
            if m2:
                parsed_ports.append({'name': m2.group(2), 'direction': m2.group(1), 'width': ''})
    
    return mod_name, params, parsed_ports

def gen_instantiation(mod_name, params, ports):
    """生成实例化模板"""
    lines = []
    lines.append(f"{mod_name} #(")
    if params:
        for i, (pname, pval) in enumerate(params):
            comma = ',' if i < len(params)-1 else ''
            lines.append(f"    .{pname}({pname.upper()}){comma}")
    else:
        lines.append("    // 无参数")
    lines.append(") u_")
    lines.append(f"{mod_name.lower()}_inst (")
    
    if ports:
        for i, p in enumerate(ports):
            comma = ',' if i < len(ports)-1 else ''
            conn = f".{p['name']}({p['name'].upper()})"
            lines.append(f"    {conn}{comma}")
    else:
        lines.append("    // 无端口")
    lines.append(");")
    return '\n'.join(lines)

def main():
    if len(sys.argv) < 2:
        print("用法: python gen_inst.py <rtl_file.v>")
        sys.exit(1)
    
    with open(sys.argv[1], 'r') as f:
        text = f.read()
    
    mod_name, params, ports = parse_module(text)
    print(f"// 模块: {mod_name}")
    print(f"// 参数: {[p[0] for p in params]}")
    print(f"// 端口: {[p['name'] for p in ports]}")
    print()
    print(gen_instantiation(mod_name, params, ports))

if __name__ == '__main__':
    main()

运行与验证

输入示例(保存为 example_module.v):

module fifo_ctrl #(
    parameter DATA_WIDTH = 8,
    parameter DEPTH = 16
)(
    input wire clk,
    input wire rst_n,
    input wire [DATA_WIDTH-1:0] wr_data,
    output reg [DATA_WIDTH-1:0] rd_data,
    output wire full,
    output wire empty
);
endmodule

运行命令:

python gen_inst.py example_module.v

预期输出(由代码逻辑直接推出):

// 模块: fifo_ctrl
// 参数: ['DATA_WIDTH', 'DEPTH']
// 端口: ['clk', 'rst_n', 'wr_data', 'rd_data', 'full', 'empty']

fifo_ctrl #(
    .DATA_WIDTH(DATA_WIDTH),
    .DEPTH(DEPTH)
) u_fifo_ctrl_inst (
    .clk(CLK),
    .rst_n(RST_N),
    .wr_data(WR_DATA),
    .rd_data(RD_DATA),
    .full(FULL),
    .empty(EMPTY)
);

验证方法:

  1. 将上述示例保存为 example_module.v,运行脚本;
  2. 检查输出中模块名、参数名、端口名是否与输入完全一致;
  3. 检查端口连接名(大写形式)是否与端口名一一对应;
  4. 将生成的实例化代码粘贴到任意Verilog文件中,确认语法正确(无缺失逗号、括号匹配)。

常见问题

  • ANSI与非ANSI风格:本脚本针对ANSI风格(端口声明在模块头中)。若输入为Verilog-1995风格(端口列表仅含名称,方向在模块体内声明),需先转换为ANSI风格或扩展解析逻辑。
  • 位宽表达式:端口位宽中的参数表达式(如 [DATA_WIDTH-1:0])会被原样保留在注释中,不影响实例化生成。
  • 多模块文件:脚本只解析第一个匹配的module声明。若文件含多个模块,可循环调用 parse_module 并配合 re.finditer 扩展。