forked from TheClams/SmartVHDL
-
Notifications
You must be signed in to change notification settings - Fork 0
/
vhdl_module.py
309 lines (290 loc) · 14.2 KB
/
vhdl_module.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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
import sublime, sublime_plugin
import re, string, os, sys, functools, mmap, imp
try:
from .util import vhdl_util
from .util import sublime_util
except ImportError:
sys.path.append(os.path.join(os.path.dirname(__file__), "util"))
import vhdl_util
import sublime_util
# Make sure util pythons are reloaded
def plugin_loaded():
imp.reload(vhdl_util)
imp.reload(sublime_util)
list_module_files = {}
lmf_update_ongoing = False
########################################
# Create module instantiation skeleton #
class VhdlModuleInstCommand(sublime_plugin.TextCommand):
#TODO: Run the search in background and keep a cache to improve performance
def run(self,edit):
global list_module_files
if len(self.view.sel())>0 :
r = self.view.sel()[0]
scope = self.view.scope_name(r.a)
if 'meta.module.inst' in scope:
self.view.run_command("vhdl_module_reconnect")
return
self.window = sublime.active_window()
# Populate the list_module_files:
# - If no folder in current project, just list open files
# - if it exist use latest version and display panel immediately while running an update
# - if not display panel only when list is ready
projname = self.window.project_file_name()
if not sublime.active_window().folders():
list_module_files['__NONE__'] = []
for v in self.window.views():
if v and v.file_name():
list_module_files['__NONE__'].append(os.path.abspath(v.file_name()))
self.on_list_done('__NONE__')
elif projname not in list_module_files:
sublime.set_timeout_async(functools.partial(self.get_list_file,projname,functools.partial(self.on_list_done,projname)), 0)
sublime.status_message('Please wait while module list is being built')
elif not lmf_update_ongoing:
# Create a copy so that the background update does not change the content of the list
list_module_files['__COPY__'] = list_module_files[projname][:]
# Start background update of the list
sublime.set_timeout_async(functools.partial(self.get_list_file,projname), 0)
# Display quick panel
self.on_list_done('__COPY__')
def get_list_file(self, projname, callback=None):
global list_module_files
global lmf_update_ongoing
lmf_update_ongoing = True
lmf = []
for folder in sublime.active_window().folders():
for root, dirs, files in os.walk(folder):
for fn in files:
if fn.lower().endswith(('.vhd','.vho','.vhdl')):
ffn = os.path.join(root,fn)
f = open(ffn)
if os.stat(ffn).st_size:
s = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
if s.find(b'entity') != -1:
lmf.append(ffn)
elif s.find(b'component') != -1:
lmf.append(ffn)
sublime.status_message('List of module files updated')
list_module_files[projname] = lmf[:]
lmf_update_ongoing = False
if callback:
callback()
def on_list_done(self,projname):
self.window.show_quick_panel(list_module_files[projname], functools.partial(self.on_select_file_done,projname))
def on_select_file_done(self, projname, index):
if index >= 0:
fname = list_module_files[projname][index]
try:
with open(fname, "r") as f:
flines = str(f.read())
self.ml=re.findall(r'^\s*entity\s+(\w+)\s+is',flines,re.MULTILINE);
if len(self.ml)<2:
self.view.run_command("vhdl_do_module_parse", {"args":{'fname': fname, 'mname':r'\w+'}})
else:
sublime.set_timeout_async(lambda: self.window.show_quick_panel(self.ml, functools.partial(self.on_select_module_done,fname)),0)
except FileNotFoundError:
print('[Smart VHDL] File {} not found for module instantiation'.format(fname))
def on_select_module_done(self, fname, index):
if index >= 0:
self.view.run_command("vhdl_do_module_parse", {"args":{'fname': fname, 'mname':self.ml[index]}})
# Parse a module declaration for port and generics
# Optionnaly ask user a value for each generics
class VhdlDoModuleParseCommand(sublime_plugin.TextCommand):
def run(self, edit, args):
self.fname = args['fname']
self.minfo = vhdl_util.get_ports_file(self.fname, args['mname'])
# print(self.minfo)
if self.minfo is not None:
self.param_value = []
settings = self.view.settings()
self.generic_explicit = settings.get('vhdl.generic_explicit',False)
if self.minfo['param'] and settings.get('vhdl.generic_fill'):
self.cnt = 0
self.show_prompt()
else:
self.view.run_command("vhdl_do_module_inst", {"args":{'minfo':self.minfo, 'pv':self.param_value, 'fname':self.fname}})
# Prompt user for a generic value
def show_prompt(self):
p = self.minfo['param'][self.cnt]
default = 'Default: {0}'.format(p['value'])
panel = sublime.active_window().show_input_panel(p['name'], default, self.on_prompt_done, None, None)
#select the whole line (to ease value change)
r = panel.line(panel.sel()[0])
panel.sel().clear()
panel.sel().add(r)
# When user has entered a generic value, called again the prompt if this is not the last generic
# Otherwise do the actual instantiation
def on_prompt_done(self, content):
if not content.startswith("Default"):
self.param_value.append({'name':self.minfo['param'][self.cnt]['name'] , 'value': content});
elif self.generic_explicit :
self.param_value.append({'name':self.minfo['param'][self.cnt]['name'] , 'value': content[9:]});
self.cnt += 1
if not self.minfo['param']:
return
if self.cnt < len(self.minfo['param']):
self.show_prompt()
else:
self.view.run_command("vhdl_do_module_inst", {"args":{'minfo':self.minfo, 'pv':self.param_value, 'fname':self.fname}})
# Actual Module instantiation
class VhdlDoModuleInstCommand(sublime_plugin.TextCommand):
def run(self, edit, args):
settings = self.view.settings()
minfo = args['minfo']
params = args['pv']
# retrieve connection
(decl,ac,wc) = self.get_connect(self.view,settings,minfo)
# print('decl = {}\nAC = {}\nwc = {}'.format(decl,ac,wc))
# Instance name
inst = '\t' + settings.get('vhdl.instance_prefix','') + minfo['name'] + settings.get('vhdl.instance_suffix','')
inst += ' : entity work.{}\n'.format(minfo['name'])
# Generic Map
if params :
inst += '\t\tgeneric map (\n'
max_len_l = max([len(x['name']) for x in params])
max_len_r = max([len(x['value']) for x in params])
for i,param in enumerate(params) :
inst += '\t\t\t{} => {}'.format(param['name'].ljust(max_len_l),param['value'].ljust(max_len_r))
if i<len(params)-1:
inst +=','
inst += '\n'
inst += '\t\t)\n'
# Port Map
if minfo['port'] :
inst += '\t\tport map (\n'
max_len_l = max([len(x['name']) for x in minfo['port']])
max_len_r = 0 if not ac else max([len(x) for x in ac])
for i,port in enumerate(minfo['port']) :
inst += '\t\t\t{} => {}'.format(port['name'].ljust(max_len_l), '' if port['name'] not in ac else ac[port['name']].ljust(max_len_r))
# Remove entry of ac if it is the same as the port (to be used by the final report)
if port['name'] in ac and ac[port['name']] == port['name']:
ac.pop(port['name'],0)
if i<len(minfo['port'])-1:
inst +=','
inst += '\n'
inst += '\t\t)'
inst += ';\n\n'
report = ''
# Insert code for module Instantiation
self.view.insert(edit, self.view.line(self.view.sel()[0]).a, inst)
# Insert signal declaration if any
if decl:
r_start = self.view.find(r'(?si)^\s*architecture\s+\w+\s+of\s+\w+\s+is(.*?)$',0, sublime.IGNORECASE)
if r_start:
# print('Start = {} = {} '.format(r_start,self.view.substr(r_start)))
# find position of last ;
r_begin = self.view.find(r'(?si)\bbegin\b',r_start.b, sublime.IGNORECASE)
r_begin2 = self.view.find(r'(?si);[^;]+\bbegin\b',r_start.b, sublime.IGNORECASE)
# print(' -> end = {} & {}'.format(r_begin,r_begin2))
if r_begin2.a > 0 and r_begin2.a < r_begin.a - 1 :
r_start.a = r_begin2.a+1
elif r_begin.a > 0 and r_start.b < r_begin.a - 1 :
r_start.a = r_begin.a-1
else:
r_start.a = r_start.b
# print(' => Start = {}'.format(r_start))
self.view.insert(edit, r_start.a, '\n'+decl)
report += 'Declaring {} signals\n'.format(len(decl.splitlines()))
else :
report += 'Unable to find declaration region:\n' + decl
if len(ac)>0 :
report+= 'Non-perfect name match for {} port(s) : {}\n'.format(len(ac),ac)
if len(wc)>0 :
report+= 'Found {} mismatch(es) for port(s): {}\n'.format(len(wc),[x for x in wc.keys()])
if report:
sublime_util.print_to_panel(report,'SmartVHDL')
# Find connection between instance port and local signal/port
def get_connect(self,view,settings,minfo):
decl = ''
ac = {} # autoconnection (entry is port name)
wc = {} # warning connection (entry is port name)
if not settings.get('vhdl.autoconnect',False) or not minfo['port']:
return (decl,ac,wc)
port_prefix = settings.get('vhdl.autoconnect_port_prefix', [])
port_suffix = settings.get('vhdl.autoconnect_port_suffix', [])
# Find local signals and port for connection
txt = self.view.substr(sublime.Region(0, self.view.size()))
info = vhdl_util.get_signals(txt)
if not info:
return (decl,ac,wc)
port_info = vhdl_util.get_ports(txt)
# TODO: handle case where entity is defined in another file ...
if not port_info:
pass
# Merge signal and port information into one dict
if port_info :
info['port'] = port_info['port']
info['param'] = port_info['param']
# Create a dictionnay of all port and signal
dict_sig = {x['name']: x for x in info['port']}
dict_sig.update({x['name']: x for x in info['signal']})
dict_sig_txt = '\n'.join(dict_sig.keys())
if info['param']:
dict_param = {x['name']: x for x in info['param']}
# For each port of the instance, find an existing port/signal to connect
# If not create a new signal and provide its declaration
for port in minfo['port']:
#Remove suffix/prefix of port name
pname = port['name']
for prefix in port_prefix:
if pname.startswith(prefix):
pname = pname[len(prefix):]
break
for suffix in port_suffix:
if pname.endswith(suffix):
pname = pname[:-len(suffix)]
break
# Check existing signals/port
ti = {'decl':None,'type':None, 'name':pname, 'tag':'signal'}
if pname in dict_sig:
ti = dict_sig[pname]
_,warn = self.check_connect(port,ti)
# Check for extended match: prefix
if not ti['decl']:
if settings.get('vhdl.autoconnect_allow_prefix',False):
sl = re.findall(r'\b(\w+_'+pname+r')\b',dict_sig_txt,flags=re.MULTILINE)
if sl :
# find smallest signal matching type of port
sl.sort(key = lambda s: len(s))
for sn in sl:
ti = dict_sig[sn]
_,warn = self.check_connect(port,ti)
if not warn:
break
# Check for extended match: suffix
if not ti['decl']:
if settings.get('vhdl.autoconnect_allow_suffix',False):
sl = re.findall(r'\b('+pname+r'_\w+)\b',dict_sig_txt,flags=re.MULTILINE)
if sl :
# find smallest signal matching type of port
sl.sort(key = lambda s: len(s))
for sn in sl:
ti = dict_sig[sn]
_,warn = self.check_connect(port,ti)
if not warn:
break
# Create a declaration when a new signal has to be created
if not ti['decl']:
d,warn = self.check_connect(port,ti)
decl += '\t{}\n'.format(d) # Add signal declaration with basic indentation (find local indentation ?)
if warn:
wc[port['name']] = warn
# Set signal name for autoconnect information
ac[port['name']] = ti['name']
return (decl,ac,wc)
def check_connect(self,port,sig):
d = re.sub(r'(?i)^\s*port\b','signal',port['decl'])
d = re.sub(r'(?i)\s+(in|out|inout)\s+',' ',d)
warn = ''
if sig['decl']:
ds = sig['decl'].lower()
if sig['tag']=='port':
ds = re.sub(r'(?i)^\s*port\b','signal',ds).lower()
ds = re.sub(r'(?i)\s+(in|out|inout)\s+',' ',ds)
if sig['dir']!=port['dir']:
warn = 'Incompatible port direction'
ds = re.sub(r'\b'+sig['name']+r'\b',port['name'],ds)
if not warn and d.lower()!=ds:
warn = 'Signal/port not matching: "{}" vs "{}"'.format(port['decl'],sig['decl'])
d += ';'
return d,warn