forked from f4pga/prjxray
-
Notifications
You must be signed in to change notification settings - Fork 1
/
segmaker.py
446 lines (382 loc) · 17 KB
/
segmaker.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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
'''
NOTE: "segments" as used in this file is mostly unrelated to tilegrid.json usage
ie tilegrid.json has names like SEG_CLBLL_L_X2Y50 where as here they are tile based and named like seg_00400100_02
Instead of using tilegrid.json "segments, segments are formed by looking for tiles that use the same address + offset
Sample segdata.txt output (from 015-clbnffmux/specimen_001/segdata_clbll_r.txt):
seg 00020880_048
bit 30_00
bit 31_49
tag CLB.SLICE_X0.AFF.DMUX.CY 1
tag CLB.SLICE_X0.BFF.DMUX.BX 0
tilegrid.json provides tile addresses
'''
import os, json, re
from prjxray import util
BLOCK_TYPES = set(('CLB_IO_CLK', 'BLOCK_RAM', 'CFG_CLB'))
def recurse_sum(x):
'''Count number of nested iterable occurances'''
if type(x) in (str, bytearray):
return 1
if type(x) in (dict, ):
return sum([recurse_sum(y) for y in x.values()])
else:
try:
return sum([recurse_sum(y) for y in x])
except TypeError:
return 1
def json_hex2i(s):
'''Convert a JSON hex literal into an integer (it can't store hex natively)'''
# TODO: maybe just do int(x, 0)
return int(s[2:], 16)
def add_site_group_zero(segmk, site, prefix, vals, zero_val, val):
'''
Correctly add tags for a multi-bit enumerated value
Naively adding them directly doesn't work correctly because overlapping bits won't solve correctly
Instead, you need to carefully diff against a known zero value
Good zero values:
-An enum known to be zero
-A site that doesn't contain the enum
segmak: Segmaker object
site: the site to add tags to
prefix: tag string to prefix onto vals
vals: all possible tag enum vals
zero_val: tag value known to have no bits set
'''
# assert zero_val in vals, "Got %s, need %s" % (zero_val, vals)
assert val in vals or val == zero_val, "Got %s, need %s" % (val, vals)
if val == zero_val:
# Zero symbol occured, none of the others did
for aval in vals:
tag = prefix + aval
segmk.add_site_tag(site, tag, aval == val)
else:
# Only add the occured symbol
tag = prefix + val
segmk.add_site_tag(site, tag, True)
if zero_val in vals:
# And zero so that it has something to solve against
tag = prefix + zero_val
segmk.add_site_tag(site, tag, False)
class Segmaker:
def __init__(self, bitsfile, verbose=None, db_root=None, part=None):
self.db_root = db_root
if self.db_root is None:
self.db_root = util.get_db_root()
self.part = part
if self.part is None:
self.part = util.get_part()
assert self.part, "No part specified."
self.verbose = verbose if verbose is not None else os.getenv(
'VERBOSE', 'N') == 'Y'
self.load_grid()
self.load_bits(bitsfile)
'''
self.tags[site][name] = value
Where:
-site: ex 'SLICE_X13Y101'
-name: ex 'CLB.SLICE_X0.AFF.DMUX.CY'
'''
self.site_tags = dict()
self.tile_tags = dict()
# output after compiling
self.segments_by_type = None
# hacky...improve if we encounter this more
self.def_bt = 'CLB_IO_CLK'
self.index_sites()
def index_sites(self):
self.verbose and print("Indexing sites")
self.sites = {}
for tilename, tiledata in self.grid.items():
for site in tiledata["sites"]:
self.sites[site] = tilename
self.verbose and print("Sites indexed")
def set_def_bt(self, block_type):
'''Set default block type when more than one block present'''
assert block_type in BLOCK_TYPES, (
"Unknown block type %r (known %r)" % (block_type, BLOCK_TYPES))
self.def_bt = block_type
def load_grid(self):
'''Load self.grid holding tile addresses'''
with open(os.path.join(self.db_root, self.part, "tilegrid.json"),
"r") as f:
self.grid = json.load(f)
assert "segments" not in self.grid, "Old format tilegrid.json"
def load_bits(self, bitsfile):
'''Load self.bits holding the bits that occured in the bitstream'''
'''
Format:
self.bits[base_frame][bit_wordidx] = set()
Where elements are (bit_frame, bit_wordidx, bit_bitidx))
bit_frame is a relatively large number forming the FDRI address
base_frame is a truncated bit_frame address of related FDRI addresses
0 <= bit_wordidx <= 100
0 <= bit_bitidx < 31
Sample bits input
bit_00020500_000_08
bit_00020500_000_14
bit_00020500_000_17
'''
self.bits = dict()
print("Loading bits from %s." % bitsfile)
with open(bitsfile, "r") as f:
for line in f:
# ex: bit_00020500_000_17
line = line.split("_")
bit_frame = int(line[1], 16)
bit_wordidx = int(line[2], 10)
bit_bitidx = int(line[3], 10)
base_frame = bit_frame & ~0x7f
self.bits.setdefault(base_frame, dict()).setdefault(
bit_wordidx, set()).add(
(bit_frame, bit_wordidx, bit_bitidx))
if self.verbose:
print(
'Loaded bits: %u bits in %u base frames' %
(recurse_sum(self.bits), len(self.bits)))
def add_site_tag(self, site, name, value):
'''
XXX: can add tags in two ways:
-By site name
-By tile name (used for pips?)
Consider splitting into two separate data structures
Record, according to value, if (site, name) exists
Ex:
self.addtag('SLICE_X13Y101', 'CLB.SLICE_X0.AFF.DMUX.CY', 1)
Indicates that the SLICE_X13Y101 site has an element called 'CLB.SLICE_X0.AFF.DMUX.CY'
'''
if '"' in site:
raise ValueError("Invalid site: %s" % site)
self.verbose and print(
'segmaker add tag: site %s tag %s = %s' % (site, name, value))
assert site in self.sites, "Unknown site %s" % (site, )
self.site_tags.setdefault(site, dict())[name] = value
def add_tile_tag(self, tile, name, value):
# TODO: test this out
# assert tile in self.grid
self.verbose and print(
'segmaker add tag: tile %s tag %s = %s' % (tile, name, value))
self.tile_tags.setdefault(tile, dict())[name] = value
def compile(self, bitfilter=None):
print("Compiling segment data.")
tags_used = set()
sites_used = set()
tile_types_found = set()
self.segments_by_type = dict()
def add_segbits(segments, segname, tiledata, bitfilter=None):
'''
Add and populate segments[segname]["bits"]
Gives all of the bits that could exist for the space we are exploring
Also add segments[segname]["tags"], but don't fill
segments is a group related to a specific tile type (ex: CLBLM_L)
It is composed of bits (possible bits) and tags (observed instances)
segments[segname]["bits"].add(bitname)
segments[segname]["tags"][tag] = value
segname: FDRI address + word offset string
tiledata: tilegrid info for this tile
'''
assert segname not in segments
segment = segments.setdefault(
segname,
{
"bits": set(),
"tags": dict(),
# verify new entries match this
"offset": bitj["offset"],
"words": bitj["words"],
"frames": bitj["frames"],
})
base_frame = json_hex2i(bitj["baseaddr"])
for wordidx in range(bitj["offset"],
bitj["offset"] + bitj["words"]):
if base_frame not in self.bits:
continue
if wordidx not in self.bits[base_frame]:
continue
for bit_frame, bit_wordidx, bit_bitidx in self.bits[
base_frame][wordidx]:
bitname_frame = bit_frame - base_frame
bitname_bit = 32 * (
bit_wordidx - bitj["offset"]) + bit_bitidx
# Skip bits above the frame limit.
if bitname_frame >= bitj["frames"]:
continue
# some bits are hard to de-correlate
# allow force dropping some bits from search space for practicality
if bitfilter is None or bitfilter(bitname_frame,
bitname_bit):
bitname = "%02d_%02d" % (bitname_frame, bitname_bit)
segment["bits"].add(bitname)
return segment
'''
XXX: wouldn't it be better to iterate over tags? Easy to drop tags
For now, add a check that all tags are used
'''
for tilename, tiledata in self.grid.items():
def getseg(segname):
if not segname in segments:
return add_segbits(
segments, segname, tiledata, bitfilter=bitfilter)
else:
segment = segments[segname]
assert segment["offset"] == bitj["offset"]
assert segment["words"] == bitj["words"]
assert segment["frames"] == bitj["frames"]
return segment
def add_tilename_tags():
self.verbose and print("Tile %s: check tags" % tilename)
segment = getseg(segname)
for name, value in self.tile_tags[tilename].items():
tags_used.add((tilename, name))
tag = "%s.%s" % (tile_type_norm, name)
segment["tags"][tag] = value
def add_site_tags():
site_prefix = site.split('_')[0]
def name_slice():
'''
Simplify SLICE names like:
-SLICE_X12Y102 => SLICE_X0
-SLICE_X13Y102 => SLICE_X1
'''
if re.match(r"SLICE_X[0-9]*[02468]Y", site):
return "SLICE_X0"
elif re.match(r"SLICE_X[0-9]*[13579]Y", site):
return "SLICE_X1"
else:
assert False, "Invalid name in %s" % site
def name_bram18():
# RAMB18_X0Y41
if re.match(r"^RAMB18_X.*Y[0-9]*[02468]$", site):
return "RAMB18_Y0"
elif re.match(r"^RAMB18_X.*Y[0-9]*[13579]$", site):
return "RAMB18_Y1"
else:
assert False, "Invalid name in %s" % site
def name_y0y1():
# RAMB18_X0Y41
m = re.match(r"^(.*)_X.*Y[0-9]*[02468]$", site)
if m:
return "%s_Y0" % m.group(1)
m = re.match(r"^(.*)_X.*Y[0-9]*[13579]$", site)
if m:
return "%s_Y1" % m.group(1)
assert 0, site
def name_default():
# most sites are unique within their tile
# TODO: maybe verify against DB?
return site_prefix
sitekey = {
'SLICE': name_slice,
'RAMB18': name_bram18,
'IOB': name_y0y1,
'IDELAY': name_y0y1,
'ODELAY': name_y0y1,
'ILOGIC': name_y0y1,
'OLOGIC': name_y0y1,
}.get(site_prefix, name_default)()
self.verbose and print(
'site %s w/ %s prefix => tag %s' %
(site, site_prefix, sitekey))
for name, value in self.site_tags[site].items():
self.verbose and print("Site %s: check tags" % site)
tags_used.add((site, name))
tag = "%s.%s.%s" % (tile_type_norm, sitekey, name)
# XXX: does this come from name?
tag = tag.replace(".SLICEM.", ".")
tag = tag.replace(".SLICEL.", ".")
segment = getseg(segname)
segment["tags"][tag] = value
sites_used.add(site)
tile_type = tiledata["type"]
tile_types_found.add(tile_type)
segments = self.segments_by_type.setdefault(tile_type, dict())
'''
Simplify names by simplifying like:
-CLBLM_L => CLB
-CENTER_INTER_R => CENTER_INTER
-CLK_HROW_TOP_R => CLK_HROW
-LIOB33 => IOB33
-LIOI3 => IOI3
'''
tile_type_norm = re.sub("(_TOP|_BOT|LL|LM)?_[LR]$", "", tile_type)
tile_type_norm = re.sub(
"_TOP_[LR]_UPPER", "_UPPER", tile_type_norm)
if tile_type_norm in ['LIOB33', 'RIOB33']:
tile_type_norm = 'IOB33'
if tile_type_norm in ['LIOB18', 'RIOB18']:
tile_type_norm = 'IOB18'
if tile_type_norm in ['LIOI3', 'RIOI3']:
tile_type_norm = 'IOI3'
if tile_type_norm in ['LIOI3_TBYTESRC', 'RIOI3_TBYTESRC']:
tile_type_norm = 'IOI3'
if tile_type_norm in ['LIOI3_TBYTETERM', 'RIOI3_TBYTETERM']:
tile_type_norm = 'IOI3'
if tile_type_norm in ['LIOI', 'RIOI']:
tile_type_norm = 'IOI'
if tile_type_norm in ['LIOI_TBYTESRC', 'RIOI_TBYTESRC']:
tile_type_norm = 'IOI'
if tile_type_norm in ['LIOI_TBYTETERM', 'RIOI_TBYTETERM']:
tile_type_norm = 'IOI'
# ignore dummy tiles (ex: VBRK)
if len(tiledata['bits']) == 0:
if self.verbose:
for site in tiledata["sites"]:
assert site not in self.site_tags, "Site %s does not have bitstream info" % site
this_tile_tags = len(self.tile_tags.get(tilename, {}))
assert this_tile_tags == 0, "Tile %s does not have bitstream info but %s tags" % (
tilename, this_tile_tags)
continue
elif len(tiledata['bits']) == 1:
bitj = list(tiledata['bits'].values())[0]
else:
assert self.def_bt in tiledata[
'bits'], 'Default block not present: %s' % self.def_bt
bitj = tiledata['bits'][self.def_bt]
# NOTE: multiple tiles may have the same base addr + offset
segname = "%s_%03d" % (
# truncate 0x to leave hex string
bitj["baseaddr"][2:],
bitj["offset"])
# process tile name tags
if tilename in self.tile_tags:
add_tilename_tags()
# process site name tags
for site in tiledata["sites"]:
if site not in self.site_tags:
continue
add_site_tags()
n_site_tags = recurse_sum(self.site_tags)
n_tile_tags = recurse_sum(self.tile_tags)
ntags = n_site_tags + n_tile_tags
if self.verbose:
assert ntags, "No tags"
print("Used %u / %u tags" % (len(tags_used), ntags))
print("Tag sites: %u" % (n_site_tags, ))
if n_site_tags:
print(' Ex: %s' % list(self.site_tags.keys())[0])
print("Tag tiles: %u" % (n_tile_tags, ))
print("Used %u sites" % len(sites_used))
print("Grid DB had %u tile types" % len(tile_types_found))
assert ntags == len(tags_used), "Unused tags, %s used out of %s" % (
len(tags_used), ntags)
def write(self, suffix=None, roi=False, allow_empty=False):
assert self.segments_by_type, 'No data to write'
if not allow_empty:
assert sum(
[len(segments) for segments in self.segments_by_type.values()
]) != 0, "Didn't generate any segments"
for segtype in self.segments_by_type.keys():
if suffix is not None:
filename = "segdata_%s_%s.txt" % (segtype.lower(), suffix)
else:
filename = "segdata_%s.txt" % (segtype.lower())
segments = self.segments_by_type[segtype]
if segments:
print("Writing %s." % filename)
with open(filename, "w") as f:
for segname, segdata in sorted(segments.items()):
# seg 00020300_010
print("seg %s" % segname, file=f)
for bitname in sorted(segdata["bits"]):
print("bit %s" % bitname, file=f)
for tagname, tagval in sorted(segdata["tags"].items()):
print("tag %s %d" % (tagname, tagval), file=f)