在工作中经常需要对网表进行分析、统计、修改,而现有的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
可以看到:
端口的数据结构:端口名字、方向、类型、最高位、最低位。具体如下:
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就是根据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,方便应用层进一步处理。基于本示例可以很方便地实现网表分析、统计、修改等高级应用。