ExASIC
分享让工作更轻松

用python实现一个verilog网表Parser

在工作中经常需要对网表进行分析、统计、修改,而现有的EDA工具却往往不够灵活。其实我们自己也可以用python来实现这个功能。今天我们来实现第一步,先做一个Verilog网表Parser。

分析网表的结构和组成

下面是一个简化的网表,但是却包括完整的语法结构。

module xor4 (a , b , c);
input  [3:0] a ;
input  [3:0] b ;
output  [3:0] c ;


XOR U0 (.A2 ( b[0] ) , .A1 ( a[0] ) , .Y ( c[0] ) ) ;
XOR U1 (.A2 ( b[1] ) , .A1 ( a[1] ) , .Y ( c[1] ) ) ;
XOR U2 (.A2 ( b[2] ) , .A1 ( a[2] ) , .Y ( c[2] ) ) ;
XOR U3 (.A2 ( b[3] ) , .A1 ( a[3] ) , .Y ( c[3] ) ) ;

endmodule


module xor8 (a, b, c);
input [7:0] a ;
input [7:0] b ;
output [7:0] c ;

xor4 U0 (.a(a[3:0]), .b(b[3:0]), .c(c[3:0]));
xor4 U0 (.a(b[7:4]), .b(b[7:4]), .c(c[7:4]));

endmodule

可以看到:

  1. Verilog网表总是由若干个module组成
  2. 每个module由模块名字、端口、内部wire定义(可选)、实例化(其它设计模块或者标准单元库模块)等几部分组成
  3. 端口由端口列表、位宽、方向等信息组成
  4. 实例化由模块名、例化名、端口连接组成

定义网表数据结构

端口

端口的数据结构:端口名字、方向、类型、最高位、最低位。具体如下:

class t_port:

    def __init__(self):
        self.name = "default"
        self.direction = "input"
        self.wire_type = "wire"
        self.msb = 0
        self.lsb = 0

连线

连线的数据结构:连线的名字、连接关系。具体如下:

class t_wire:

    def __init__(self, name):
        self.name = name
        self.conn = []

一根连线可以连接到多个pin或port,所以用列表来存储。

实例化

实例化的数据结构:实例化名字、模块名、端口映射关系。具体如下:

class t_inst:

    def __inst__(self, name):
        self.name = name
        self.module_name = "default"
        self.port_map = []

端口映射关系是多组"端口<->连线"对,因而也用列表存储。

模块

模块的数据结构:模块名、端口列表、连线列表、实例化列表。具体如下:

class t_module:

    def __init__(self, name):
        self.name = name
        self.ports = []
        self.wires = []
        self.insts = []

顶层模块与普通的模块并无差异,只是没有被实例化而已。这个角度看,如果没有冗余模块时,就可以自动分析出顶层模块。否则就需要额外指定顶层模块名字。

Parser的实现

所谓Parser就是根据Verilog语法规则解析网表,把解析到的信息存入上面定义的数据结构中。这个数据结构叫做抽象语法树(AST)。

第一步,读入Verilog网表,并把网表拆分成多个单独的module。如下,判断是否遇到endmodule。

def read_netlist(self, file_name):
    self.file_name = file_name

    f = open(self.file_name, "r")
    self.nl = f.readlines()

def split_netlist(self):
    m = []
    t1 = len(self.nl)
    for i in range(t1):
        l = self.nl[i]
        m.append(l)
        if l.startswith('endmodule'):
            self.read_module(m)

第二步,解析module。先去除注释,合并所有行,再按分号来重新分行。根据module xxx来读取模块名。根据input/output/inout来读取端口。根据wire来读取连线。根据xxx yyy (...)。读到这些信息后,存储进上面定义的数据结构。详细代码如下,注意各个正则的用法。

def read_module(self, m):
    #remove comment lines and empty lines
    m1 = []
    for l in m:
        if re.match(r'^\s*\/\/', l) or re.match(r'^\s*\n$', l):
            pass
        else:
            m1.append(l)

    #divide module by ';'
    n = ''.join(m1)
    m1 = n.split(';')

    #print("len of m1: {}".format(len(m1)))

    for i in range(len(m1)):
        #module name
        ret = re.match(r'^module\s+(\w+)\s+', m1[i], re.S)
        if ret:
            module_name = ret.group(1)
            module_temp = t_module(module_name)
            self.designs.append(module_temp)
            continue

        #port
        found = 0
        m1_temp = re.sub(r'\n', '', m1[i], re.S)
        ret = re.match(r'^(input|output|inout)\s+\[(\d+):(\d+)\]\s+(\w+)', m1_temp, re.S)
        if ret:
            port_direction = ret.group(1)
            port_msb = ret.group(2)
            port_lsb = ret.group(3)
            port_name = ret.group(4)
            found = 1

        else:
            ret = re.match(r'^(input|output|inout)\s+\[(\d+)\]\s+(\w+)', m1_temp, re.S)
            if ret:
                port_direction = ret.group(1)
                port_msb = ret.group(2)
                port_lsb = ret.group(2)
                port_name = ret.group(3)
                found = 1

            else:
                ret = re.match(r'^(input|output|inout)\s+(\w+)', m1_temp, re.S)
                if ret:
                    port_direction = ret.group(1)
                    port_msb = '0'
                    port_lsb = '0'
                    port_name = ret.group(2)
                    found = 1

        if found == 1:
            port_msb = int(port_msb)
            port_lsb = int(port_lsb)

            port_temp = t_port()
            port_temp.new_port(port_name, port_direction, "wire", port_msb, port_lsb)
            module_temp.add_port(port_temp)

            for i in range(port_lsb, port_msb):
                wire_temp = t_wire("{}{}".format(port_name, i))
                wire_temp.add_conn(None, "{}[{}]".format(port_name, i))
                module_temp.add_wire(wire_temp)

            continue

        #wire
        ret = re.match(r'^wire\s+\[(\d+):(\d+)\]\s+(\w+)', m1_temp, re.S)
        if ret:
            port_direction = "input"
            port_msb = ret.group(1)
            port_lsb = ret.group(2)
            port_name = ret.group(3)

            port_msb = int(port_msb)
            port_lsb = int(port_lsb)

            for i in range(port_lsb, port_msb):
                wire_temp = t_wire("{}{}".format(port_name, i))
                wire_temp.add_conn(None, "{}[{}]".format(port_name, i))
                module_temp.add_wire(wire_temp)

            continue

        #endmodule
        ret = re.match(r'^\s*endmodule', m1_temp, re.S)
        if ret:
            continue

        #inst
        ret = re.match(r'^\s*(\w+)\s+(\w+)\s+\((.*)\)\s*$', m1_temp, re.S)
        if ret:
            #print(m1_temp)
            mod_name = ret.group(1)
            inst_name = ret.group(2)
            port_map_s = ret.group(3)

            inst_temp = t_inst(inst_name);
            inst_temp.new_inst(mod_name, inst_name);

            port_map_s = re.sub(r'\n', '', port_map_s, re.S)
            port_map_s = re.sub(r'\s', '', port_map_s, re.S)
            #print(port_map_s)
            port_map_l = port_map_s.split(',')
            for pm in port_map_l:
                r = re.match(r'\.(\w+)\((.*)\)', pm) #.* match xx[2], xx[2:0]
                #print(r)
                if r:
                    inst_temp.add_port_map(r.group(1), r.group(2))
                    if module_temp.exist_wire(r.group(2)):
                        i = module_temp.find_wire(r.group(2))
                        module_temp.wires[i].add_conn(inst_name, r.group(1))
                    else:
                        wire_temp = t_wire(r.group(2))
                        wire_temp.add_conn(inst_name, r.group(1))
                        module_temp.add_wire(wire_temp)


            #inst_temp.print_this()
            module_temp.add_inst(inst_temp)
        else:
            print(m1_temp)

第三步,抽象语法树AST的顶层,定义如下,网表文件名和module AST列表。这样应用层就可以从上而下遍历整个数据结构。

class parser:

    def __init__(self):
        self.file_name = ""
        self.designs = [] #multi modules

应用层测试:打印子模块名

读入并解析Verilog测试网表test.v,然后print出相关信息。

from netlist import t_port, t_wire, t_inst, t_module
import parser

if __name__ == '__main__':
    parser = parser.parser()
    parser.read_netlist("test.v")

    parser.designs[0].print_this()
    parser.designs[1].print_this()

下一步

本示例用正则和字符串函数实现了解析Verilog网表,并构建了网表的AST,方便应用层进一步处理。基于本示例可以很方便地实现网表分析、统计、修改等高级应用。

本示例代码开源于:https://github.com/chenfengrugao/nlviewer

阅读数:
更多文章:文章目录
解惑专区
(支持markdown插入源代码)
欢迎使用ExASIC订阅服务
仅用于ExASIC最新文章通知,方便及时阅读。
友情链接: IC技术圈问答ReCclayCrazyFPGA