-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxdcGen.py
207 lines (190 loc) · 6.38 KB
/
xdcGen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import argparse
import os
from xdcLib import *
import datetime
import re
deviceCls = None
def is_valid_expression(expression : str) -> bool:
pattern = "^([a-zA-Z_])([a-zA-Z0-9_]*)$"
return bool(re.match(pattern, expression))
def strip_blank(line):
return line.strip()
def parse_sugar(pins):
ret = []
#print(deviceCls)
for pin in pins:
if pin == "clk":
ret += deviceCls.ClockMap()
elif pin == "seg7":
ret += deviceCls.Seg7Map()
elif pin == "seg7en":
ret += deviceCls.Seg7EnMap()
elif pin == "vga":
ret += deviceCls.vgaMap()
else:
ret.append(pin)
return ret
def parse_expression(exp, isLeft): # an express mean a single or aggregated name of pins
if not '|' in exp:
# simple exp
return [strip_blank(exp)]
else:
# complex exp
# find the first | and the last | in the exp
first_idx = exp.find("||")
last_idx = exp.rfind("||")
if first_idx == -1 or last_idx == -1: # actually, should be both or none
# not Python expression
first_idx, last_idx = exp.find('|'), exp.rfind('|')
if last_idx - first_idx <= 1:
print(f"Invalid expression: {exp}")
return None
exp = exp.split('|')
name, rest = exp[0], exp[1]
name = strip_blank(name)
rest = rest.split(':')
if not is_valid_expression(name):
print("Illegal pin name: " + name + ".")
print(f"Invalid expression: {exp}")
return None
if len(rest) > 3 or len(rest) < 2:
print(f"Invalid expression: {exp}")
return None
elif len(rest) == 3:
start, end, step = rest[0], rest[1], rest[2]
try:
start, end, step = int(start), int(end), int(step)
except ValueError:
print(f"Invalid expression: {exp}")
return None
elif len(rest) == 2:
start, end = rest[0], rest[1]
try:
start, end = int(start), int(end)
except ValueError:
print(f"Invalid expression: {exp}")
return None
step = 1
if start > end:
step = -step
i = start # LED |2:1:1|
ret = []
while i >= min(start, end) and i <= max(start, end):
if isLeft:
ret.append(f"{name}{i}")
else:
ret.append(f"{name}[{i}]")
i += step
return ret
else:
# Python expression
if last_idx - first_idx <= 2:
print(f"Invalid expression: {exp}")
return None
name, rest = exp[:first_idx], exp[first_idx+2:last_idx]
name = strip_blank(name)
try:
rest = eval("list(" + rest + ")")
except:
print(f"Invalid expression: {exp}")
return None
ret = []
for i in rest:
if isLeft:
ret.append(f"{name}{i}")
else:
ret.append(f"{name}[{i}]")
return ret
# should not be here
def parse_script_line(line):
idx_comment = line.find('#')
idx = line.find('~')
if idx == -1:
return False
#split the line into two parts
left, right = line[:idx], line[idx+1:]
left_items = left.split(';')
left_pins = []
parsed = None
for item in left_items:
item = item.strip()
if not item:
continue
parsed = parse_expression(item, True)
if parsed:
left_pins += parsed
else:
return False
right_items = right.split(';')
right_pins = []
for item in right_items:
item = item.strip()
if not item:
continue
parsed = parse_expression(item, False)
if parsed:
right_pins += parsed
else:
return False
left_pins = parse_sugar(left_pins)
if len(left_pins) != len(right_pins):
print(f"LHS and RHS have different sum of pins.")
return False
elif len(left_pins) == 0:
print(f"No pins found")
return False
print(f"Left pins: {left_pins}")
print(f"Right pins: {right_pins}")
return left_pins, right_pins
def main():
parser = argparse.ArgumentParser()
parser.add_argument('file_path', type=str)
parser.add_argument('-n', type=str)
parser.add_argument('-o', type=str)
args = parser.parse_args()
try:
with open(args.file_path, 'r') as file:
content = file.read()
except FileNotFoundError:
print(f"File {args.file_path} not found.")
return
device = "Artix-7 100T"
if args.n:
device = args.n
global deviceCls
for cls in FPGAdevice.__subclasses__():
if cls.name == device:
deviceCls = cls
break
if not deviceCls:
print(f"Device {device} not supported")
print(f"Please check https://github.com/Jackcuii/xdcGenerator for latest version.")
print(f"BTW, Your generous contribution is appreciated, too.")
return
print(f"Generating XDC file for {device}")
#print(f"{deviceCls}")
# split the content by lines
content = content.split('\n')
# parse each line
output = ""
for i in range(len(content)):
line = content[i]
idx_comment = line.find('#')
if idx_comment != -1:
line = line[:idx_comment]
if not strip_blank(line):
continue
parsed = parse_script_line(line)
if not parsed:
print(f"ERROR raised at line {i+1}")
return
left_pins, right_pins = parsed
for j in range(len(left_pins)):
output += f"{deviceCls.lookup(left_pins[j]).getStr(right_pins[j])}\n"
# by default write to file named date and time
output_path = args.o if args.o else os.path.join(os.getcwd(), f"xdc/xdcGen_{device}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.xdc")
with open(output_path, 'w') as output_file:
output_file.write(output)
print(f"XDC file generated at {output_path}")
if __name__ == "__main__":
main()