VIM插件 -- 自动生成verilog module的testbench

VIM插件 -- 自动生成verilog module的testbench

@(VIM)

1. 动机

软件语言都有各自好用的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的正则表达式写法。话不多说,直接上代码。

2. 代码

"-------------------------------------------------------------------
"    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

3. 使用方法

以上脚本需要复制粘贴到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,按\(\color{red}{\tb}\)即可生成当前verilog的testbench模板。

4. 效果

需要生成testbench的verilog文件:

按下\(\color{red}{\tb}\)后:


后面只需要添加特定的激励即可,初始化和例化都已经自动生成了。

5. 说明

由于只是自己一时兴起而写,虽然用到现在没出过错,但不排除某些特殊情况会出错。此外,对verilog 1995标准的写法支持仍不够完善,实测module中带有参数的1995标准写法在run该脚本时会有问题,其他情况暂时没发现问题。对于2001标准的写法应该是没有太大问题(如果有发现问题欢迎留言,以便改进)。如果有自己的testbench的风格,可以直接修改代码中tb生成部分的代码。

posted @ 2020-06-19 00:25  love小酒窝  阅读(2231)  评论(3编辑  收藏  举报