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)
);
验证方法:
- 将上述示例保存为
example_module.v,运行脚本; - 检查输出中模块名、参数名、端口名是否与输入完全一致;
- 检查端口连接名(大写形式)是否与端口名一一对应;
- 将生成的实例化代码粘贴到任意Verilog文件中,确认语法正确(无缺失逗号、括号匹配)。
常见问题
- ANSI与非ANSI风格:本脚本针对ANSI风格(端口声明在模块头中)。若输入为Verilog-1995风格(端口列表仅含名称,方向在模块体内声明),需先转换为ANSI风格或扩展解析逻辑。
- 位宽表达式:端口位宽中的参数表达式(如
[DATA_WIDTH-1:0])会被原样保留在注释中,不影响实例化生成。 - 多模块文件:脚本只解析第一个匹配的module声明。若文件含多个模块,可循环调用
parse_module并配合re.finditer扩展。