学习了一些Python的基础语法之后,确实被Python优美的表达方式所吸引,所以想用Python来干点什么,之前一直想用MATLAB脚本来完成TestBench的自动生成,这里正好用Python来完成。
该TestBench主要包含以下三个部分:
- 基本的时钟clk和复位rst的生成
- 读取一个文件中的数据
- 提取一个模块的接口信号并将其实例化
前两个部分使用普通的文件写入操作就可以完成,第三个部分稍微复杂一点,需要用到正则表达式提取模块的名字、信号的名字和位宽等。总的来说,这是一段很简单的代码。
另外:可将第三部分用作Verilog子模块的顶层文件自动生成,省去复制粘贴连线的麻烦。
1. 程序结构和运行流程
1.1 程序结构
该程序有三个文件。
主文件 | 字符串文件 | 模块提取函数文件 |
verilog_tb_gen.py | config.py | extract_interface.py |
_
- 字符串文件中存着一些固定的打印信息
- 模块提取函数文件中则完成了整个模块接口信息的提取和打印
- 主文件完成调度管理
1.2 运行流程
- 打开或新建一个sim文件;
- 从config中提取生成时钟和复位的字符串并写入sim文件中;
- 同上:提取memory初始化字符串;
- 同上:提取memory数据读取的always块字符串;
- 调用模块提取函数提取指定文件的接口信息并写入sim文件;
- 关闭文件。
_
模块提取函数流程:
该函数有三个部分,每个部分的流程基本一致。
- 将提取文件中的字符按行分离为一个字符串列表,并赋给一个变量;
- 预编译含有接口信息的正则表达式;
- 利用for循环在每一行里面去搜索该正则表达式,如果搜索到了则进入下一步;
- 将搜索到的字符串按照空格或者逗号分离成一个个单独的字符串,这样就可以根据下标提取接口的信息;
- 将提取到的信息写入sim文件。
2. 流程中用到的操作说明
按1.2的流程:
1. 打开、关闭、及写入等文件操作与其他语言基本一致
2. 2、3、4步就是基本的打印和文本替换,与一般的print一样;
3. 模块接口信息提取用到的操作
- 读取并分行,用到了read()和splitlines():
- 正则表达式,注意re.match和re.search的区别,以及为什么要预编译正则表达式
菜鸟教程正则表达式
正则表达式说明:
下面是该程序中最长的正则表达式
re_interface = re.compile('(input|output)(\s+|\[\d+:0\]|\s+\[\d+:0\]|\[\d+:0\]\s+|\s+\[\d+:0\]\s+)\w+(,(\s+|)\w+){0,10}')
该正则表达式匹配的是输入和输出信号的名字和位宽信息,因为考虑了Verilog书写的各种情况,所以该正则表达式才那么长,如果接口很规范,其实很短就可以了。
先注意正则表达式中的三个符号:’\’ ‘|’ ‘()’
- 反斜杠’\’:用作转义字符,如表达式中的\s+表示匹配至少一个空格,\w+匹配至少一个字母、数字或下划线,\d+匹配至少一个数字;
- 竖线’|’:表示或操作,如(input|output)即可匹配输入也可匹配输出;
- 圆括号()”:逻辑分离
Verilog输入输出的可能写法有以下几种:
// 第一个括号中匹配的情况
input sig_1;
output sig_2;
// 第二个括号中匹配的情况,按从左到右的顺序
input signal_1; // 位宽为1,中间只有空格
input[1:0]signal_2; // 完全没有空格
input [1:0]signal_3; // 左边有空格
input[1:0] signal_4; // 右边有空格
input [1:0] signal_5; // 两边都有空格
// 第二个括号和第三个括号中间的 '\w+' 是匹配第一个信号的名字
// 第三个括号匹配的是多个信号中第一个信号后面的情况,如 ', signal_7',花括号里数字表示最多匹配10个
input signal_6, signal_7; // 一行有多个信号,信号间有逗号和空格
input signal_8,signal_9; // 信号间只有逗号
3. 附录
verilog_tb_gen.py代码
'''
Verilog testbench generator
'''
import re
import config
import extract_interface
# parameter
NAME = 'fir_sim' # tb name
PERIOD = 10 # clock period: ns
RST_DELAY = 200 # reset delay : ns
DATA_WIDTH = 32 # rom data width
DATA_LEN = 301 # rom data length
PATH = r'D:\my_prj\my_filter_hdl_1\my_filter_hdl_1.srcs\msg.dat'
# memory path
# open and truncate file
fp = open('%s.v' % NAME, 'w')
fp.truncate()
# print clk and rst
fp.write(config.mod_clk_rst % (NAME, PERIOD, RST_DELAY))
fp.write(config.divide)
# print rom
fp.write(config.memory % (DATA_WIDTH - 1, DATA_LEN, PATH.replace('\\','/')))
fp.write(config.divide)
# print source data
fp.write(config.src_data % (DATA_WIDTH - 1, DATA_LEN))
fp.write(config.divide)
fp.write(config.divide)
'''
Extract module interface
'''
extract_interface.extract_print('fir_hdl.v', 0, fp) # Extract file1
extract_interface.extract_print('fir.v', 1, fp) # Extract file2
# The end, close the file
fp.write('\n\nendmodule')
fp.close()
config.py代码
'''
Verilog testbench generator
String
'''
# module clk rst
mod_clk_rst = 'module %s;\n\
parameter PERIOD = %d;\n\
reg clk = 1;\n\
reg rst = 1;\n\n\
initial begin\n\
forever\n\
#(PERIOD/2) clk = ~clk;\n\
end\n\n\
initial begin\n\
#%d\n\
rst = 0;\n\
end\n\n\
'
# divide
divide = '//------------------------------------------------------------\n'
# memory
memory = 'reg [%d:0] datain[0:%d];\n\
initial begin\n\
$readmemb("%s", datain);\n\
end\n\n\
'
# source data
src_data = "reg\t[15:0]\t\ti;\n\
reg\t[%d:0]\t\tsource_data;\n\
reg\t\t\t\tsource_data_vld;\n\n\
always @ (posedge clk) begin\n\
if (rst) begin\n\
i <= 'd0;\n\
source_data <= 'd0;\n\
source_data_vld <= 'd0;\n\
end\n\
else if (i < %d) begin\n\
i <= i + 1;\n\
source_data <= datain[i];\n\
source_data_vld <= 1;\n\
end\n\
else begin\n\
i <= i;\n\
source_data <= 'd0;\n\
source_data_vld <= 'd0;\n\
end\n\
end\n\n\
"
extract_interface.py代码
'''
Verilog testbench generator
Extract module interface
'''
import re
def extract_print( path, u, fp ): # u是例化模块时的编号
with open(path, 'r') as dotv:
split_as_lines = dotv.read().splitlines()
# Extract output wire
re_wire = re.compile('output(\s+|\[\d+:0\]|\s+\[\d+:0\]|\[\d+:0\]\s+|\s+\[\d+:0\]\s+)\w+(,(\s+|)\w+){0,10}')
for i in range(len(split_as_lines)):
x = re_wire.search(split_as_lines[i])
if x:
wire_list = re.split(r'[\s\,]+', x.group(0))
if re.match('\[', wire_list[1]):
width_info = wire_list[1]
indx = 2
else:
width_info = '\t'
indx = 1
for j in range(len(wire_list) - indx):
fp.write('wire\t%s\t\t%-20s;\n' % (width_info, wire_list[j + indx]))
fp.write('\n')
# Extract module name
re_module_name = re.compile('module\s+\w+') # 预编译正则表达式
for i in range(len(split_as_lines)):
y = re_module_name.search(split_as_lines[i])
if y:
module_name = re.split('\s+', y.group(0))[1]
fp.write('%-19su%-6d(\n' % (module_name, u)) # 打印module name
# Extract interface
re_interface = re.compile('(input|output)(\s+|\[\d+:0\]|\s+\[\d+:0\]|\[\d+:0\]\s+|\s+\[\d+:0\]\s+)\w+(,(\s+|)\w+){0,10}')
k = 0
for i in range(len(split_as_lines)):
z = re_interface.search(split_as_lines[i])
if z:
interface_string = re.sub(r'\[\d+:0\]', ' ', z.group(0))
interface_list = re.split(r'[\s\,]+', interface_string)
for j in range(len(interface_list) - 1):
if (k == 0):
fp.write('\t.%-21s( %-20s)\n' % (interface_list[j+1], interface_list[j+1]))
else:
fp.write('\t,.%-20s( %-20s)\n' % (interface_list[j+1], interface_list[j+1]))
k += 1
fp.write('\t);\n\n')
return
生成的tb文件
注意该tb还不是一个完整的tb
module fir_sim;
parameter PERIOD = 10;
reg clk = 1;
reg rst = 1;
initial begin
forever
#(PERIOD/2) clk = ~clk;
end
initial begin
#200
rst = 0;
end
//------------------------------------------------------------
reg [31:0] datain[0:301];
initial begin
$readmemb("D:/my_prj/my_filter_hdl_1/my_filter_hdl_1.srcs/msg.dat", datain);
end
//------------------------------------------------------------
reg [15:0] i;
reg [31:0] source_data;
reg source_data_vld;
always @ (posedge clk) begin
if (rst) begin
i <= 'd0;
source_data <= 'd0;
source_data_vld <= 'd0;
end
else if (i < 301) begin
i <= i + 1;
source_data <= datain[i];
source_data_vld <= 1;
end
else begin
i <= i;
source_data <= 'd0;
source_data_vld <= 'd0;
end
end
//------------------------------------------------------------
//------------------------------------------------------------
wire [31:0] fir_out ;
fir_hdl u0 (
.clk ( clk )
,.fir_in ( fir_in )
,.fir_out ( fir_out )
);
wire [31:0] fir_out_TDATA ;
wire fir_in_TREADY ;
wire fir_out_TVALID ;
fir u1 (
.fir_in_TDATA ( fir_in_TDATA )
,.fir_out_TDATA ( fir_out_TDATA )
,.ap_clk ( ap_clk )
,.ap_rst_n ( ap_rst_n )
,.fir_in_TVALID ( fir_in_TVALID )
,.fir_in_TREADY ( fir_in_TREADY )
,.fir_out_TVALID ( fir_out_TVALID )
,.fir_out_TREADY ( fir_out_TREADY )
);
endmodule
提取的源文件接口
fir.v
module fir (
fir_in_TDATA,
fir_out_TDATA,
ap_clk,
ap_rst_n,
fir_in_TVALID,
fir_in_TREADY,
fir_out_TVALID,
fir_out_TREADY
);
input [31:0] fir_in_TDATA;
output [31:0] fir_out_TDATA;
input ap_clk;
input ap_rst_n;
input fir_in_TVALID;
output fir_in_TREADY;
output fir_out_TVALID;
input fir_out_TREADY;
fir_hdl.v
module fir_hdl(
input clk,
input [31:0] fir_in,
output [31:0] fir_out
);