赞
踩
@(VIM)
软件语言都有各自好用的IDE,各种自动补全,高亮,语法检查。而苦逼的ICer大多还操着远古时期的VIM写着verilog。也是,硬件语言本身就小众,即使是xilinx, altera等大厂的vivado, quartus等大牌软件,自带的代码编辑器也不是很友好。好在号称编辑器之神的VIM提供了丰富的定制化功能,vimscript可以实现诸多媲美成熟IDE的功能。此外,vimscript还配备了很多语言的编程接口,perl,python的脚本都可以在vimscript里运行,这也使得开发插件的门槛降低了不少。
在某次写verilog的testbench时,觉得从写完一个模块,到验证其功能的路上花了大量时间在一些重复的coding上,testbench中大部分内容都是重复模块的端口定义,完全可以让脚本去干,于是花了半天尝试着用python写了一套自动生成verilog modual 的testbench的脚本,嵌入到vim中,通过简单的命令就可以实现繁琐的testbench的模板生成。顺便,,,也巩固一下python的正则表达式写法。话不多说,直接上代码。
"------------------------------------------------------------------- " auto testbench (python) "------------------------------------------------------------------- :nnoremap <leader>tb :call Autotb()<cr> function! Autotb() python << EOF import vim,re # 清除所有注释内容 def clear_commonts(lines): block_comment_index = [] # record block comment index # remove // comment for i in range(len(lines)): lines[i] = re.sub('//.*$','',lines[i]) all_pairs = re.findall('/\*',lines[i]) if(all_pairs != []): next_index = 0 new_line = lines[i] for j in range(len(all_pairs)): # print(new_line) find_index = re.search('/\*',new_line).span() next_index = find_index[1] new_line = new_line[next_index:] block_comment_index.append(i) if(re.search('\*/',new_line)!=None): # find paired */ block_comment_index.append(i) else: if(re.search('\*/',lines[i])!=None): # find paired */ block_comment_index.append(i) # print(block_comment_index) if(len(block_comment_index)%2!=0): raise('ERROR: /* and */ not paired') else: for i in range(int(len(block_comment_index)/2)): left_index = block_comment_index[i*2] right_index = block_comment_index[i*2+1] if(left_index==right_index): # inline /* */ lines[left_index] = re.sub('/\*.*\*/','',lines[left_index]) else: for j in range(left_index+1,right_index): # delet comment in block /* */ lines[j] = '' lines[left_index] = re.sub('/\*.*$','',lines[left_index]) lines[right_index] = re.sub('^.*\*/','',lines[right_index]) return lines # 找到模块名 def find_inst_name(line): pattern = re.compile('(module)\s+(\w+)') if(pattern.search(line)!=None): return pattern.search(line).group(2) else: return '' # 文本行转换为字符串 def lines2string(lines): string = '' for l in lines: string += l return string # 获取输入输出端口名以及位宽,符号 def find_port_and_width(s): signed_flag = 0 l_tmp = re.sub('(input)|(output)|(inout)','',s,1) # count=1 will not sub these word in signal name l_tmp = re.sub('(wire)|(reg)','',l_tmp,1) if(re.search('\sunsigned[\s\[]',l_tmp)!=None): signed_flag = 'unsigned' elif(re.search('\ssigned[\s\[]',l_tmp)!=None): signed_flag = 'signed' else: signed_flag = ' ' l_tmp = re.sub('(signed)|(unsigned)','',l_tmp,1) l_tmp = re.sub('\s','',l_tmp) if(re.search('\[.+\]',l_tmp)!=None): width = re.search('\[.+\]',l_tmp).group(0) l_tmp = re.sub('\[.+\]','',l_tmp) else: width = '1' #print(l_tmp) if(re.search('\w+(?=[;,\s\)])',l_tmp)!=None): port_name = re.search('\w+(?=[;,\s\)])',l_tmp).group(0) else: raise('Port name lost!') print(width,port_name,signed_flag) return width,port_name,signed_flag # 寻找模块例化中定义段parameter变量 def find_parameter(s): l_tmp = re.sub('\s','',s) name_search = re.search('\w+(?=\=)',l_tmp) val_search = re.search('(?<=\=)[\w\+\-\*/\%\$\'`]+',l_tmp) if(name_search!=None): name = name_search.group(0) else: name = '' if(val_search!=None): val = val_search.group(0) else: val = '' return name,val # 寻找模块的输入输出 def find_input_output(lines): inst_dict = {'name':'','input':[],'output':[],'inout':[],'para':[]} string = lines2string(lines) inst_dict['name'] = find_inst_name(string) if inst_dict['name'] == '': raise('Inst name lost!') output_pattern = re.compile('\Woutput[\[\s][\w\s\[\]:\+\-\*/\(\)%\{\}\'`]*[,;]') # include 0-9 a-z A-Z [:] \n input_pattern = re.compile('\Winput[\[\s][\w\s\[\]:\+\-\*/\(\)%\{\}\'`]*[,;]') # include 0-9 a-z A-Z [:] \n inout_pattern = re.compile('\Winout[\[\s][\w\s\[\]:\+\-\*/\(\)%\{\}\'`]*[,;]') # include 0-9 a-z A-Z [:] \n input_list = input_pattern.findall(string) output_list = output_pattern.findall(string) inout_list = inout_pattern.findall(string) para_search_end_index = input_pattern.search(string).span()[0] # print (string[:para_search_end_index]) para_pattern = re.compile('\Wparameter\s+[\s\w\[\]:\+\-\*/\(\)%\{\}\=,\'`]*') para_search = para_pattern.search(string[:para_search_end_index]) if(para_search!=None): para_string = para_search.group(0) #print(para_string) para_string = ' '+para_string para_string = re.sub('\sparameter\s','',para_string) para_list = para_string.split(',') # print(para_list) for l in para_list: inst_dict['para'].append(find_parameter(l)) else: inst_dict['para'] = [] for l in input_list: inst_dict['input'].append(find_port_and_width(l)) for l in output_list: inst_dict['output'].append(find_port_and_width(l)) for l in inout_list: inst_dict['inout'].append(find_port_and_width(l)) return inst_dict def max_len(ll,mode=0): max_len = 1 for l in ll: if(len(l[mode])>max_len): max_len = len(l[mode]) return max_len def build_input_port_tb(in_list,mode='input'): max_in_len_width = max_len(in_list,0) max_in_len_signed = max_len(in_list,2) string = '' net_type = {'input':'reg','output':'wire','inout':'wire'} for in_port in in_list: in_name = in_port[1] width = in_port[0] if in_port[0]!='1' else '' signed_type = in_port[2] added_len_signed = max_in_len_signed - len(signed_type) added_len_width = max_in_len_width - len(width) s = '%s %s '%(net_type[mode],signed_type) + added_len_signed*' ' s += '%s'%(width) + added_len_width*' ' + ' %s;\n'%(in_name) #print(max_in_len_width) #print(max_in_len_signed) string += s return string def build_para_tb(in_list): max_in_len = max_len(in_list,0) string = '' for in_port in in_list: in_name = in_port[0] number = in_port[1] added_len = max_in_len - len(in_name) s = 'parameter %s '%(in_name) + added_len*' '+'= %s;\n'%(number) string += s return string # 寻找模块中的clk和rst def find_clk_and_rst(in_list): clk = '' rst = '' for l in in_list: name = l[1] if(re.search('clk',name,re.I)!=None): clk = name break for l in in_list: name = l[1] if(re.search('rst',name,re.I)!=None): rst = name break return clk,rst def input_initial_tb(in_list,rst): string = 'initial begin\n' for l in in_list: if(l[1]!=rst): s = ' %s = 0;\n'%l[1] string += s rst_kind = re.search('(?<=rst)_?n',rst,re.I)==None #1:rst, 0:rst_n string += ' %s = %d;\n #4 %s = %d; #2 %s = %d;\n'%(rst,1-rst_kind,rst,rst_kind,rst,1-rst_kind) string += 'end\n' return string # 生成模块例化代码 def build_inst(inst_dict): inst_name = inst_dict['name'] name_len = len(inst_name) string = inst_name if(inst_dict['para']==[]): para_string = ' ' else: para_string = ' #(' para_max_in_len = max_len(inst_dict['para'],0) for i,para_port in enumerate(inst_dict['para']): para_name = para_port[0] number = para_port[1] added_len = para_max_in_len - len(para_name) s = (i!=0)*(name_len+3)*' '+' .%s '%(para_name) + added_len*' '+'( '+ para_name +added_len*' '+' )'+(i!=len(inst_dict['para'])-1)*','+(i==len(inst_dict['para'])-1)*')'+'\n' para_string += s string += para_string string += 'U_'+ inst_name.upper()+'_0\n' # port inst port_list = inst_dict['input']+inst_dict['output']+inst_dict['inout'] port_max_in_len = max_len(port_list,1) port_string = '(' for i,port in enumerate(port_list): port_name = port[1] added_len = port_max_in_len - len(port_name) # print(port_list) s = (i!=0)*' '+' .%s '%(port_name) + added_len*' '+'( '+ port_name + added_len*' '+' )'+(i!=len(port_list)-1)*','+(i==len(port_list)-1)*');'+'\n' port_string += s string += port_string return string # 增加dumpvar相关命令,vcs+verdi调试需要 def add_dumpvars(): string = '\ninitial begin\n $fsdbDumpvars();\n $fsdbDumpMDA();\n $dumpvars();\n #1000 $finish;\nend' return string # 增加头部信息 def add_head(name,author='Yicheng Lu'): import time now_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) year = now_time[0:4] string = "// -----------------------------------------------------------------\n// Copyright (c) %s .\n// ALL RIGHTS RESERVED\n// -----------------------------------------------------------------\n// Filename : %s.v\n// Author : %s\n// Created On : %s\n// Last Modified :\n// -----------------------------------------------------------------\n// Description:\n//\n// -----------------------------------------------------------------\n"%(year,name,author,now_time) return string # 生成tb def build_tb(inst_dict): string = add_head(name=inst_dict['name']+'_tb') string += '\n`timescale 1ns/1ps\n\n' string += 'module %s();\n\n'%(inst_dict['name']+'_tb') string += build_para_tb(inst_dict['para']) string += '\n' string += build_input_port_tb(inst_dict['input'],'input') string += '\n' string += build_input_port_tb(inst_dict['output'],'output') string += '\n' string += build_input_port_tb(inst_dict['inout'],'inout') clk,rst = find_clk_and_rst(inst_dict['input']) string += '\nalways #1 %s = ~%s;\n'%(clk,clk) string += input_initial_tb(inst_dict['input'],rst) string += '\n' string += build_inst(inst_dict) string += '\n' string += add_dumpvars() string += '\n' string += '\nendmodule\n' return string lines = [] for l in vim.current.buffer[:]: lines.append(l+'\n') # string in buffer do not contain '\n' clear_lines = clear_commonts(lines) inst_dict = find_input_output(clear_lines) tb_string = build_tb(inst_dict) # 调用vim的python接口,生成一个新的窗口,在新窗口构建tb vim.command("badd %s"%(inst_dict['name']+'_tb.v')) buffer_num = len(vim.buffers) print(buffer_num) vim.command("tabnew") vim.command("b"+str(buffer_num)) vim.current.buffer[:] = tb_string.split('\n') #vim.command('NERDTree') EOF endfunction
以上脚本需要复制粘贴到vim的配置文件.vimrc中即可生效。需要系统装有python,如果系统装的python是python3,需要修改
:nnoremap <leader>tb :call Autotb()<cr>
function! Autotb()
python << EOF
为
:nnoremap <leader>tb :call Autotb()<cr>
function! Autotb()
python3 << EOF
使用时,用vim打开某个.v,按KaTeX parse error: Undefined control sequence: \tb at position 13: \color{red}{\̲t̲b̲}即可生成当前verilog的testbench模板。
需要生成testbench的verilog文件:
按下KaTeX parse error: Undefined control sequence: \tb at position 13: \color{red}{\̲t̲b̲}后:
后面只需要添加特定的激励即可,初始化和例化都已经自动生成了。
由于只是自己一时兴起而写,虽然用到现在没出过错,但不排除某些特殊情况会出错。此外,对verilog 1995标准的写法支持仍不够完善,实测module中带有参数的1995标准写法在run该脚本时会有问题,其他情况暂时没发现问题。对于2001标准的写法应该是没有太大问题(如果有发现问题欢迎留言,以便改进)。如果有自己的testbench的风格,可以直接修改代码中tb生成部分的代码。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。