Merge pull request #167 from litghost/fasm_refactor3

Refactor FASM handling to use new FASM library.
This commit is contained in:
John McMaster 2018-10-22 14:09:58 -07:00 committed by GitHub
commit 2b19547541
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 874 additions and 696 deletions

3
.gitmodules vendored
View File

@ -13,3 +13,6 @@
[submodule "third_party/yaml-cpp"]
path = third_party/yaml-cpp
url = https://github.com/jbeder/yaml-cpp.git
[submodule "third_party/fasm"]
path = third_party/fasm
url = https://github.com/SymbiFlow/fasm.git

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3
import sys, re
from prjxray.segmaker import Segmaker
segmk = Segmaker("design.bits")

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, os, re
import re
from prjxray.segmaker import Segmaker

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, re
import sys
from prjxray.segmaker import Segmaker

View File

@ -10,8 +10,6 @@ LDPE Primitive: Transparent Data Latch with Asynchronous Preset and Gate Enable
from prims import *
import sys, re
from prjxray.segmaker import Segmaker
segmk = Segmaker("design.bits")

View File

@ -1,7 +1,5 @@
import random
random.seed(0)
import os
import re
from prjxray import util
from prims import *
@ -14,8 +12,10 @@ f.write("i,prim,loc,bel,init\n")
def gen_slices():
for _tile_name, site_name, _site_type in util.gen_sites(['SLICEL',
'SLICEM']):
for _tile_name, site_name, _site_type in util.get_roi().gen_sites([
'SLICEL',
'SLICEM',
]):
yield site_name

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3
import sys, re
from prjxray.segmaker import Segmaker
segmk = Segmaker("design.bits")

View File

@ -1,7 +1,5 @@
import random
random.seed(0)
import os
import re
from prjxray import util
CLBN = 40
@ -9,8 +7,8 @@ print('//Requested CLBs: %s' % str(CLBN))
def gen_slices():
for _tile_name, site_name, _site_type in util.gen_sites(['SLICEL',
'SLICEM']):
for _tile_name, site_name, _site_type in util.get_roi().gen_sites(
['SLICEL', 'SLICEM']):
yield site_name

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3
import sys, re
from prjxray.segmaker import Segmaker
segmk = Segmaker("design.bits")

View File

@ -1,7 +1,5 @@
import random
random.seed(0)
import os
import re
from prjxray import util
CLBN = 400
@ -9,8 +7,8 @@ print('//Requested CLBs: %s' % str(CLBN))
def gen_slices():
for _tile_name, site_name, _site_type in util.gen_sites(['SLICEL',
'SLICEM']):
for _tile_name, site_name, _site_type in util.get_roi().gen_sites(
['SLICEL', 'SLICEM']):
yield site_name

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3
import sys, re
from prjxray.segmaker import Segmaker
segmk = Segmaker("design.bits")

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3
import sys, os, re
from prjxray.segmaker import Segmaker
from prjxray import util

View File

@ -1,7 +1,5 @@
import random
random.seed(0)
import os
import re
from prjxray import util
CLBN = 400
@ -9,8 +7,10 @@ print('//Requested CLBs: %s' % str(CLBN))
def gen_slices():
for _tile_name, site_name, _site_type in util.gen_sites(['SLICEL',
'SLICEM']):
for _tile_name, site_name, _site_type in util.get_roi().gen_sites([
'SLICEL',
'SLICEM',
]):
yield site_name

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3
import sys, os, re
from prjxray.segmaker import Segmaker
from prjxray import util

View File

@ -1,7 +1,5 @@
import random
random.seed(0)
import os
import re
from prjxray import util
CLBN = 400
@ -9,8 +7,8 @@ print('//Requested CLBs: %s' % str(CLBN))
def gen_slices():
for _tile_name, site_name, _site_type in util.gen_sites(['SLICEL',
'SLICEM']):
for _tile_name, site_name, _site_type in util.get_roi().gen_sites(
['SLICEL', 'SLICEM']):
yield site_name

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3
import sys, os, re
from prjxray.segmaker import Segmaker
segmk = Segmaker("design.bits")

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3
import sys, re, os
from prjxray.segmaker import Segmaker
segmk = Segmaker("design.bits")

View File

@ -17,8 +17,6 @@ Note: LUT6 was added to try to simplify reduction, although it might not be need
import random
random.seed(0)
import os
import re
from prjxray import util
CLBN = 50
@ -26,7 +24,8 @@ print('//Requested CLBs: %s' % str(CLBN))
def gen_slicems():
for _tile_name, site_name, _site_type in util.gen_sites(['SLICEM']):
for _tile_name, site_name, _site_type in util.get_roi().gen_sites(
['SLICEM']):
yield site_name

View File

@ -5,8 +5,6 @@
# Can we find instance where they are not aliased?
WA7USED = 0
import sys, re, os
from prjxray.segmaker import Segmaker
segmk = Segmaker("design.bits")

View File

@ -1,7 +1,5 @@
import random
random.seed(0)
import os
import re
from prjxray import util
CLBN = 50
@ -9,7 +7,8 @@ print('//Requested CLBs: %s' % str(CLBN))
def gen_slicems():
for _tile_name, site_name, _site_type in util.gen_sites(['SLICEM']):
for _tile_name, site_name, _site_type in util.get_roi().gen_sites(
['SLICEM']):
yield site_name

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, re
import re
from prjxray.segmaker import Segmaker

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, re, os
import re, os
from prjxray.segmaker import Segmaker

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, os, re
import os, re
from prjxray.segmaker import Segmaker

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, os, re
import os, re
from prjxray.segmaker import Segmaker

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, os, re
import re
from prjxray.segmaker import Segmaker

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, os, re
import re
from prjxray.segmaker import Segmaker

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python3
import sys, os, re
from prjxray.segmaker import Segmaker
segmk = Segmaker("design.bits")

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, os, re
import os
from prjxray.segmaker import Segmaker

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, os, re
import sys
from prjxray.segmaker import Segmaker

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys, os, re
import sys
from prjxray.segmaker import Segmaker

View File

@ -0,0 +1,7 @@
set -ex
BIT_IN=$1
BITS=$(tempfile --suffix .bits)
${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o ${BITS} -z -y ${BIT_IN}
${XRAY_BITS2FASM} ${BITS}

View File

@ -27,7 +27,7 @@ echo "Design .fasm: $fasm_in"
echo "Harness .bit: $bit_in"
echo "Out .bit: $bit_out"
${XRAY_DIR}/tools/fasm2frame.py $fasm_in roi_partial.frm
${XRAY_FASM2FRAMES} --sparse $fasm_in roi_partial.frm
${XRAY_TOOLS_DIR}/xc7patch \
--part_name ${XRAY_PART} \

View File

@ -57,8 +57,8 @@ test -z "$(fgrep CRITICAL vivado.log)"
${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o design.bits -z -y design.bit
${XRAY_SEGPRINT} -zd design.bits >design.segp
${XRAY_DIR}/tools/segprint2fasm.py design.segp design.fasm
${XRAY_DIR}/tools/fasm2frame.py design.fasm design.frm
${XRAY_DIR}/utils/bits2fasm.py --verbose design.bits > design.fasm
${XRAY_DIR}/utils/fasm2frames.py design.fasm design.frm
# Hack to get around weird clock error related to clk net not found
# Remove following lines:
#set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]

31
prjxray/bitstream.py Normal file
View File

@ -0,0 +1,31 @@
# Break frames into WORD_SIZE bit words.
WORD_SIZE_BITS = 32
# How many 32-bit words for frame in a 7-series bitstream?
FRAME_WORD_COUNT = 101
def load_bitdata(f):
""" Read bit file and return bitdata map.
bitdata is a map of of two sets.
The map key is the frame address.
The first sets are the word columns that have any bits set.
Word columsn are WORD_SIZE_BITS wide.
The second sets are bit index within the frame and word if it is set.
"""
bitdata = dict()
for line in f:
line = line.split("_")
frame = int(line[1], 16)
wordidx = int(line[2], 10)
bitidx = int(line[3], 10)
if frame not in bitdata:
bitdata[frame] = set(), set()
bitdata[frame][0].add(wordidx)
bitdata[frame][1].add(wordidx * WORD_SIZE_BITS + bitidx)
return bitdata

View File

@ -2,6 +2,7 @@ import os.path
import json
from prjxray import grid
from prjxray import tile
from prjxray import tile_segbits
from prjxray import site_type
from prjxray import connections
@ -37,7 +38,9 @@ class Database(object):
self.tile_types = None
self.tile_types = {}
self.tile_segbits = {}
self.site_types = {}
for f in os.listdir(self.db_root):
if f.endswith('.json') and f.startswith('tile_type_'):
tile_type = f[len('tile_type_'):-len('.json')].lower()
@ -47,6 +50,11 @@ class Database(object):
if not os.path.isfile(segbits):
segbits = None
ppips = os.path.join(
self.db_root, 'ppips_{}.db'.format(tile_type))
if not os.path.isfile(ppips):
ppips = None
mask = os.path.join(
self.db_root, 'mask_{}.db'.format(tile_type))
if not os.path.isfile(mask):
@ -60,6 +68,7 @@ class Database(object):
self.tile_types[tile_type.upper()] = tile.TileDbs(
segbits=segbits,
ppips=ppips,
mask=mask,
tile_type=tile_type_file,
)
@ -124,3 +133,10 @@ class Database(object):
site_type_data = json.load(f)
return site_type.SiteType(site_type_data)
def get_tile_segbits(self, tile_type):
if tile_type not in self.tile_segbits:
self.tile_segbits[tile_type] = tile_segbits.TileSegbits(
self.tile_types[tile_type.upper()])
return self.tile_segbits[tile_type]

148
prjxray/fasm_assembler.py Normal file
View File

@ -0,0 +1,148 @@
import fasm
from prjxray import bitstream
from prjxray import grid
class FasmLookupError(Exception):
pass
class FasmInconsistentBits(Exception):
pass
def init_frame_at_address(frames, addr):
'''Set given frame to 0 if not initialized '''
if not addr in frames:
frames[addr] = [0 for _i in range(bitstream.FRAME_WORD_COUNT)]
class FasmAssembler(object):
def __init__(self, db):
self.db = db
self.grid = db.grid()
self.frames = {}
self.frames_line = {}
def get_frames(self, sparse=False):
if not sparse:
frames = self.frames_init()
else:
frames = {}
for (frame_addr, word_addr, bit_index), is_set in self.frames.items():
init_frame_at_address(frames, frame_addr)
if is_set:
frames[frame_addr][word_addr] |= 1 << bit_index
return frames
def frames_init(self):
'''Set all frames to 0'''
frames = {}
for bits_info in self.grid.iter_all_frames():
for coli in range(bits_info.bits.frames):
init_frame_at_address(
frames, bits_info.bits.base_address + coli)
return frames
def frame_set(self, frame_addr, word_addr, bit_index, line):
'''Set given bit in given frame address and word'''
assert bit_index is not None
key = (frame_addr, word_addr, bit_index)
if key in self.frames:
if self.frames[key] != 1:
raise FasmInconsistentBits(
'FASM line "{}" wanted to set bit {} but was cleared by FASM line "{}"'
.format(
line,
key,
self.frames_line[key],
))
return
self.frames[key] = 1
self.frames_line[key] = line
def frame_clear(self, frame_addr, word_addr, bit_index, line):
'''Set given bit in given frame address and word'''
assert bit_index is not None
key = (frame_addr, word_addr, bit_index)
if key in self.frames:
if self.frames[key] != 0:
raise FasmInconsistentBits(
'FASM line "{}" wanted to clear bit {} but was set by FASM line "{}"'
.format(
line,
key,
self.frames_line[key],
))
return
self.frames[key] = 0
self.frames_line[key] = line
def enable_feature(self, tile, feature, address, line):
gridinfo = self.grid.gridinfo_at_tilename(tile)
# TODO: How to determine if the feature targets BLOCK_RAM segment type?
bits = gridinfo.bits[grid.BlockType.CLB_IO_CLK]
seg_baseaddr = bits.base_address
seg_word_base = bits.offset
def update_segbit(bit):
'''Set or clear a single bit in a segment at the given word column and word bit position'''
# Now we have the word column and word bit index
# Combine with the segments relative frame position to fully get the position
frame_addr = seg_baseaddr + bit.word_column
# 2 words per segment
word_addr = seg_word_base + bit.word_bit // bitstream.WORD_SIZE_BITS
bit_index = bit.word_bit % bitstream.WORD_SIZE_BITS
if bit.isset:
self.frame_set(frame_addr, word_addr, bit_index, line)
else:
self.frame_clear(frame_addr, word_addr, bit_index, line)
segbits = self.db.get_tile_segbits(gridinfo.tile_type)
db_k = '%s.%s' % (gridinfo.tile_type, feature)
try:
for bit in segbits.feature_to_bits(db_k, address):
update_segbit(bit)
except KeyError:
raise FasmLookupError(
"Segment DB %s, key %s not found from line '%s'" %
(gridinfo.tile_type, db_k, line))
def parse_fasm_filename(self, filename):
for line in fasm.parse_fasm_filename(filename):
if not line.set_feature:
continue
line_strs = tuple(fasm.fasm_line_to_string(line))
assert len(line_strs) == 1
line_str = line_strs[0]
parts = line.set_feature.feature.split('.')
tile = parts[0]
feature = '.'.join(parts[1:])
# canonical_features flattens multibit feature enables to only
# single bit features, which is what enable_feature expects.
#
# canonical_features also filters out features that are not enabled,
# which are no-ops.
for flat_set_feature in fasm.canonical_features(line.set_feature):
address = 0
if flat_set_feature.start is not None:
address = flat_set_feature.start
self.enable_feature(tile, feature, address, line_str)

View File

@ -0,0 +1,133 @@
import re
import fasm
from prjxray import bitstream
def mk_fasm(tile_name, feature):
""" Convert matches tile and feature to FasmLine tuple. """
# Seperate addressing of multi-bit features:
# TILE.ALUT[0] -> ('TILE', 'ALUT', '0')
# TILE.ALUT.SMALL -> ('TILE', 'ALUT.SMALL', None)
m = re.match(r'([A-Za-z0-9_]+).([^\[]+)(\[[0-9]+\])?', feature)
tag_post = m.group(2)
address = None
if m.group(3) is not None:
address = int(m.group(3)[1:-1])
feature = '{}.{}'.format(tile_name, tag_post)
return fasm.FasmLine(
set_feature=fasm.SetFasmFeature(
feature=feature,
start=address,
end=None,
value=1,
value_format=None,
),
annotations=None,
comment=None)
class FasmDisassembler(object):
""" Given a Project X-ray data, outputs FasmLine tuples for bits set. """
def __init__(self, db):
self.db = db
self.grid = self.db.grid()
self.segment_map = self.grid.get_segment_map()
self.decode_warnings = set()
def find_features_in_tile(self, tile_name, bits, bitdata, verbose=False):
gridinfo = self.grid.gridinfo_at_tilename(tile_name)
try:
tile_segbits = self.db.get_tile_segbits(gridinfo.tile_type)
except KeyError as e:
if not verbose:
return
if gridinfo.tile_type in self.decode_warnings:
return
comment = " WARNING: failed to load DB for tile type {}".format(
gridinfo.tile_type)
yield fasm.FasmLine(
set_feature=None,
annotations=None,
comment=comment,
)
yield fasm.FasmLine(
set_feature=None,
annotations=[
fasm.Annotation('missing_segbits', gridinfo.tile_type),
fasm.Annotation('exception', str(e)),
],
comment=None,
)
self.decode_warnings.add(gridinfo.tile_type)
return
for ones_matched, feature in tile_segbits.match_bitdata(bits, bitdata):
for frame, bit in ones_matched:
bitdata[frame][1].remove(bit)
yield mk_fasm(tile_name=tile_name, feature=feature)
def find_features_in_bitstream(self, bitdata, verbose=False):
frames = set(bitdata.keys())
tiles_checked = set()
while len(frames) > 0:
frame = frames.pop()
# Skip frames that were emptied in a previous iteration.
if not bitdata[frame]:
continue
# Iterate over all tiles that use this frame.
for bits_info in self.segment_map.segment_info_for_frame(frame):
# Don't examine a tile twice
if bits_info.tile in tiles_checked:
continue
# Check if this frame has any data for the relevant tile.
any_column = False
for word_idx in range(bits_info.bits.words):
if word_idx + bits_info.bits.offset in bitdata[frame][0]:
any_column = True
break
if not any_column:
continue
tiles_checked.add(bits_info.tile)
for fasm_line in self.find_features_in_tile(
bits_info.tile, bits_info.bits, bitdata,
verbose=verbose):
yield fasm_line
if len(bitdata[frame][1]) > 0 and verbose:
# Some bits were not decoded, add warning and annotations to
# FASM.
yield fasm.FasmLine(
set_feature=None,
annotations=None,
comment=" In frame 0x{:08x} {} bits were not converted.".
format(
frame,
len(bitdata[frame][1]),
))
for bit in bitdata[frame][1]:
wordidx = bit // bitstream.WORD_SIZE_BITS
bitidx = bit % bitstream.WORD_SIZE_BITS
annotation = fasm.Annotation(
'unknown_bit', '{:08x}_{}_{}'.format(
frame, wordidx, bitidx))
yield fasm.FasmLine(
set_feature=None,
annotations=[annotation],
comment=None,
)

View File

@ -1,7 +1,20 @@
from collections import namedtuple
import enum
from prjxray import segment_map
class BlockType(enum.Enum):
# Frames describing CLB features, interconnect, clocks and IOs.
CLB_IO_CLK = 'CLB_IO_CLK'
# Frames describing block RAM initialization.
BLOCK_RAM = 'BLOCK_RAM'
GridLoc = namedtuple('GridLoc', 'grid_x grid_y')
GridInfo = namedtuple('GridInfo', 'segment sites tile_type in_roi')
GridInfo = namedtuple('GridInfo', 'segment bits sites tile_type in_roi')
Bits = namedtuple('Bits', 'base_address frames offset words')
BitsInfo = namedtuple('BitsInfo', 'segment_type tile bits')
class Grid(object):
@ -15,6 +28,14 @@ class Grid(object):
self.tilegrid = tilegrid
self.loc = {}
self.tileinfo = {}
# Map of segment name to tiles in that segment
self.segments = {}
# Map of (base_address, segment type) -> segment name
self.base_addresses = {}
# Map of base_address -> (segment type, segment name)
self.base_addresses = {}
for tile in self.tilegrid:
tileinfo = self.tilegrid[tile]
@ -27,8 +48,28 @@ class Grid(object):
else:
in_roi = True
bits = {}
if 'segment' in tileinfo:
if tileinfo['segment'] not in self.segments:
self.segments[tileinfo['segment']] = []
self.segments[tileinfo['segment']].append(tile)
if 'bits' in tileinfo:
for k in tileinfo['bits']:
segment_type = BlockType(k)
base_address = int(tileinfo['bits'][k]['baseaddr'], 0)
bits[segment_type] = Bits(
base_address=base_address,
frames=tileinfo['bits'][k]['frames'],
offset=tileinfo['bits'][k]['offset'],
words=tileinfo['bits'][k]['words'],
)
self.tileinfo[tile] = GridInfo(
segment=tileinfo['segment'] if 'segment' in tileinfo else None,
bits=bits,
sites=tileinfo['sites'],
tile_type=tileinfo['type'],
in_roi=in_roi,
@ -64,3 +105,15 @@ class Grid(object):
def gridinfo_at_tilename(self, tilename):
return self.tileinfo[tilename]
def iter_all_frames(self):
for tile, tileinfo in self.tileinfo.items():
for segment_type, bits in tileinfo.bits.items():
yield BitsInfo(
segment_type=segment_type,
tile=tile,
bits=bits,
)
def get_segment_map(self):
return segment_map.SegmentMap(self)

59
prjxray/roi.py Normal file
View File

@ -0,0 +1,59 @@
class Roi(object):
""" Object that represents a Project X-ray ROI.
Can be used to iterate over tiles and sites within an ROI.
"""
def __init__(self, db, x1, x2, y1, y2):
self.grid = db.grid()
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
def tile_in_roi(self, grid_loc):
""" Returns true if grid_loc (GridLoc tuple) is within the ROI. """
x = grid_loc.grid_x
y = grid_loc.grid_y
return self.x1 <= x and x <= self.x2 and self.y1 <= y and y <= self.y2
def gen_tiles(self, tile_types=None):
''' Yield tile names within ROI.
tile_types: list of tile types to keep, or None for all
'''
for tile_name in self.grid.tiles():
loc = self.grid.loc_of_tilename(tile_name)
if not self.tile_in_roi(loc):
continue
gridinfo = self.grid.gridinfo_at_loc(loc)
if tile_types is not None and gridinfo.tile_type not in tile_types:
continue
yield tile_name
def gen_sites(self, site_types=None):
''' Yield (tile_name, site_name, site_type) within ROI.
site_types: list of site types to keep, or None for all
'''
for tile_name in self.grid.tiles():
loc = self.grid.loc_of_tilename(tile_name)
if not self.tile_in_roi(loc):
continue
gridinfo = self.grid.gridinfo_at_loc(loc)
for site_name, site_type in gridinfo.sites.items():
if site_types is not None and site_type not in site_types:
continue
yield (tile_name, site_name, site_type)

View File

@ -14,9 +14,7 @@ tilegrid.json provides tile addresses
'''
import os, json, re
XRAY_DATABASE = os.getenv("XRAY_DATABASE")
XRAY_DIR = os.getenv("XRAY_DIR")
from prjxray import util
BLOCK_TYPES = set(('CLB_IO_CLK', 'BLOCK_RAM', 'CFG_CLB'))
@ -41,7 +39,11 @@ def json_hex2i(s):
class Segmaker:
def __init__(self, bitsfile, verbose=None):
def __init__(self, bitsfile, verbose=None, db_root=None):
self.db_root = db_root
if self.db_root is None:
self.db_root = util.get_db_root()
self.verbose = verbose if verbose is not None else os.getenv(
'VERBOSE', 'N') == 'Y'
self.load_grid()
@ -60,9 +62,7 @@ class Segmaker:
def load_grid(self):
'''Load self.grid holding tile addresses'''
print("Loading %s grid." % XRAY_DATABASE)
with open("%s/database/%s/tilegrid.json" % (XRAY_DIR, XRAY_DATABASE),
"r") as f:
with open(os.path.join(self.db_root, "tilegrid.json"), "r") as f:
self.grid = json.load(f)
assert "segments" not in self.grid, "Old format tilegrid.json"

20
prjxray/segment_map.py Normal file
View File

@ -0,0 +1,20 @@
from intervaltree import IntervalTree, Interval
from prjxray import bitstream
class SegmentMap(object):
def __init__(self, grid):
self.segment_tree = IntervalTree()
for bits_info in grid.iter_all_frames():
self.segment_tree.add(
Interval(
begin=bits_info.bits.base_address,
end=bits_info.bits.base_address + bits_info.bits.frames,
data=bits_info,
))
def segment_info_for_frame(self, frame):
""" Return all bits info that match frame address. """
for frame in self.segment_tree[frame]:
yield frame.data

View File

@ -2,7 +2,7 @@ from collections import namedtuple
import json
from prjxray import lib
""" Database files available for a tile """
TileDbs = namedtuple('TileDbs', 'segbits mask tile_type')
TileDbs = namedtuple('TileDbs', 'segbits ppips mask tile_type')
Pip = namedtuple(
'Pip', 'name net_to net_from can_invert is_directional is_pseudo')

142
prjxray/tile_segbits.py Normal file
View File

@ -0,0 +1,142 @@
from collections import namedtuple
from prjxray import bitstream
import enum
class PsuedoPipType(enum.Enum):
ALWAYS = 'always'
DEFAULT = 'default'
HINT = 'hint'
def read_ppips(f):
ppips = {}
for l in f:
l = l.strip()
if not l:
continue
feature, ppip_type = l.split(' ')
ppips[feature] = PsuedoPipType(ppip_type)
return ppips
Bit = namedtuple('Bit', 'word_column word_bit isset')
def parsebit(val):
'''Return "!012_23" => (12, 23, False)'''
isset = True
# Default is 0. Skip explicit call outs
if val[0] == '!':
isset = False
val = val[1:]
# 28_05 => 28, 05
seg_word_column, word_bit_n = val.split('_')
return Bit(
word_column=int(seg_word_column),
word_bit=int(word_bit_n),
isset=isset,
)
def read_segbits(f):
segbits = {}
for l in f:
# CLBLM_L.SLICEL_X1.ALUT.INIT[10] 29_14
l = l.strip()
if not l:
continue
parts = l.split(' ')
assert len(parts) > 1
segbits[parts[0]] = [parsebit(val) for val in parts[1:]]
return segbits
class TileSegbits(object):
def __init__(self, tile_db):
self.segbits = {}
self.ppips = {}
self.feature_addresses = {}
if tile_db.ppips is not None:
with open(tile_db.ppips) as f:
self.ppips = read_ppips(f)
if tile_db.segbits is not None:
with open(tile_db.segbits) as f:
self.segbits = read_segbits(f)
for feature in self.segbits:
sidx = feature.rfind('[')
eidx = feature.rfind(']')
if sidx != -1:
assert eidx != -1
base_feature = feature[:sidx]
if base_feature not in self.feature_addresses:
self.feature_addresses[base_feature] = {}
self.feature_addresses[base_feature][int(
feature[sidx + 1:eidx])] = feature
def match_bitdata(self, bits, bitdata):
""" Return matching features for tile bits data (grid.Bits) and bitdata.
See bitstream.load_bitdata for details on bitdata structure.
"""
for feature, segbit in self.segbits.items():
match = True
for query_bit in segbit:
frame = bits.base_address + query_bit.word_column
bitidx = bits.offset * bitstream.WORD_SIZE_BITS + query_bit.word_bit
if frame not in bitdata:
match = not query_bit.isset
if match:
continue
else:
break
found_bit = bitidx in bitdata[frame][1]
match = found_bit == query_bit.isset
if not match:
break
if not match:
continue
def inner():
for query_bit in segbit:
if query_bit.isset:
frame = bits.base_address + query_bit.word_column
bitidx = bits.offset * bitstream.WORD_SIZE_BITS + query_bit.word_bit
yield (frame, bitidx)
yield (tuple(inner()), feature)
def feature_to_bits(self, feature, address=0):
if feature in self.ppips:
return
if address == 0 and feature in self.segbits:
for bit in self.segbits[feature]:
yield bit
else:
for bit in self.segbits[self.feature_addresses[feature][address]]:
yield bit

View File

@ -1,10 +1,12 @@
import os
import re
import os
import json
from .roi import Roi
from .db import Database
DB_PATH = "%s/%s" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"))
def get_db_root():
return "%s/%s" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"))
def roi_xy():
@ -16,9 +18,6 @@ def roi_xy():
return (x1, x2), (y1, y2)
(ROI_X1, ROI_X2), (ROI_Y1, ROI_Y2) = roi_xy()
def slice_xy():
'''Return (X1, X2), (Y1, Y2) from XRAY_ROI, exclusive end (for xrange)'''
# SLICE_X12Y100:SLICE_X27Y149
@ -30,47 +29,10 @@ def slice_xy():
return ((ms[0], ms[2] + 1), (ms[1], ms[3] + 1))
def tile_in_roi(tilej):
x = int(tilej['grid_x'])
y = int(tilej['grid_y'])
return ROI_X1 <= x <= ROI_X2 and ROI_Y1 <= y <= ROI_Y2
def load_tilegrid():
return json.load(open('%s/tilegrid.json' % DB_PATH))
def gen_tiles(tile_types=None, tilegrid=None):
'''
tile_types: list of tile types to keep, or None for all
tilegrid: cache the tilegrid database
'''
tilegrid = tilegrid or load_tilegrid()
for tile_name, tilej in tilegrid.items():
if tile_in_roi(tilej) and (tile_types is None
or tilej['type'] in tile_types):
yield (tile_name, tilej)
def gen_sites(site_types=None, tilegrid=None):
'''
site_types: list of site types to keep, or None for all
tilegrid: cache the tilegrid database
'''
tilegrid = tilegrid or load_tilegrid()
for tile_name, tilej in tilegrid.items():
if not tile_in_roi(tilej):
continue
for site_name, site_type in tilej['sites'].items():
if site_types is None or site_type in site_types:
yield (tile_name, site_name, site_type)
#print(list(gen_tiles(['CLBLL_L', 'CLBLL_R', 'CLBLM_L', 'CLBLM_R'])))
#print(list(gen_sites(['SLICEL', 'SLICEM'])))
#print(list(gen_sites(['SLICEM'])))
def get_roi():
(x1, x2), (y1, y2) = roi_xy()
db = Database(get_db_root())
return Roi(db=db, x1=x1, x2=x2, y1=y1, y2=y2)
# we know that all bits for CLB MUXes are in frames 30 and 31, so filter all other bits

View File

@ -1,4 +1,5 @@
futures
intervaltree
numpy
progressbar2
pyjson5

1
third_party/fasm vendored Submodule

@ -0,0 +1 @@
Subproject commit f78c27ecc34236df3cd4c845c13bdf279d30608c

View File

@ -1,309 +1,53 @@
#!/usr/bin/env python3
'''
Take raw .bits files and decode them to higher level functionality
This output is intended for debugging and not directly related to FASM
However, as of 2018-10-16, the output is being parsed to create FASM,
so be mindful when changing output format
TODO:
Take raw .bits files and decode them to FASM.
'''
import sys, os, json, re
import os
import fasm
from prjxray import db
from prjxray import fasm_disassembler
from prjxray import bitstream
class NoDB(Exception):
pass
def run(db_root, bits_file, verbose, canonical):
disassembler = fasm_disassembler.FasmDisassembler(db.Database(db_root))
with open(bits_file) as f:
bitdata = bitstream.load_bitdata(f)
def line(s=''):
print(s)
def comment(s):
print('# %s' % s)
enumdb = dict()
# TODO: migrate to library
def process_db(tile_type, process):
if tile_type in ('INT_L', 'INT_R'):
# interconnect
fn = "%s/%s/segbits_int_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type[-1].lower())
else:
# sites
fn = "%s/%s/segbits_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type.lower())
if not os.path.exists(fn):
raise NoDB(tile_type)
with open(fn, "r") as f:
for line in f:
process(line)
def get_enums(tile_type):
if tile_type in enumdb:
return enumdb[tile_type]
enumdb[tile_type] = {}
def process(l):
# CLBLM_L.SLICEL_X1.ALUT.INIT[10] 29_14
parts = l.strip().split()
name = parts[0]
bit_vals = parts[1:]
# Assumption
# only 1 bit => non-enumerated value
enumdb[tile_type][name] = len(bit_vals) != 1
process_db(tile_type, process)
return enumdb[tile_type]
def isenum(tilename, tag):
return get_enums(tilename)[tag]
# cache
segbitsdb = dict()
def get_database(tile_type):
if tile_type in segbitsdb:
return segbitsdb[tile_type]
ret = list()
def process(l):
ret.append(l.split())
process_db(tile_type, process)
assert len(ret)
segbitsdb[tile_type] = ret
return ret
def mk_fasm(segj, entry):
tile_name = segj['tile_name']
# ex: CLBLL_L.SLICEL_X0.AFF.DMUX.O6
tag = entry[0]
m = re.match(r'([A-Za-z0-9_]+)[.](.*)', tag)
# tile_type = m.group(1)
# the postfix, O6 in the above example
tag_post = m.group(2)
if not isenum(segj['type'], tag):
return '%s.%s 1' % (tile_name, tag_post)
else:
# Make the selection an argument of the configruation
m = re.match(r'(.*)[.]([A-Za-z0-9_]+)', tag_post)
which = m.group(1)
value = m.group(2)
return '%s.%s %s' % (tile_name, which, value)
def mk_segbits(seginfo, bitdata):
baseframe = int(seginfo["baseaddr"][0], 16)
basewordidx = int(seginfo["baseaddr"][1])
numframes = int(seginfo["frames"])
numwords = int(seginfo["words"])
segbits = set()
for frame in range(baseframe, baseframe + numframes):
if frame not in bitdata:
continue
for wordidx in range(basewordidx, basewordidx + numwords):
if wordidx not in bitdata[frame]:
continue
for bitidx in bitdata[frame][wordidx]:
segbits.add(
"%02d_%02d" %
(frame - baseframe, 32 * (wordidx - basewordidx) + bitidx))
return segbits
def tagmatch(entry, segbits):
for bit in entry[1:]:
if bit[0] != "!" and bit not in segbits:
return False
if bit[0] == "!" and bit[1:] in segbits:
return False
return True
def tag_matched(entry, segbits):
for bit in entry[1:]:
if bit[0] != "!":
segbits.remove(bit)
decode_warnings = set()
def seg_decode(seginfo, segbits, verbose=False):
fasms = set()
# already failed?
if seginfo["type"] in decode_warnings:
return fasms
try:
db = get_database(seginfo["type"])
except NoDB:
verbose and comment(
"WARNING: failed to load DB for %s" % seginfo["type"])
decode_warnings.add(seginfo["type"])
return fasms
for entry in db:
if not tagmatch(entry, segbits):
continue
tag_matched(entry, segbits)
#fasms.add('%s.%s 1' % (seginfo['tile_name'], entry[0]))
fasm = mk_fasm(seginfo, entry)
fasms.add(fasm)
return fasms
def handle_segment(segname, grid, bitdata, verbose=False):
assert segname
# only print bitstream tiles
if segname not in grid["segments"]:
return
seginfo = grid["segments"][segname]
segbits = mk_segbits(seginfo, bitdata)
fasms = seg_decode(seginfo, segbits, verbose=verbose)
# Found something to print?
if len(segbits) == 0 and len(fasms) == 0:
return
line('')
comment("seg %s" % (segname, ))
for fasm in sorted(fasms):
line(fasm)
if verbose and len(segbits) > 0:
comment('%u unknown bits' % len(segbits))
for bit in sorted(segbits):
comment("bit %s" % bit)
def load_bitdata(bits_file):
bitdata = dict()
with open(bits_file, "r") as f:
for line in f:
line = line.split("_")
frame = int(line[1], 16)
wordidx = int(line[2], 10)
bitidx = int(line[3], 10)
if frame not in bitdata:
bitdata[frame] = dict()
if wordidx not in bitdata[frame]:
bitdata[frame][wordidx] = set()
bitdata[frame][wordidx].add(bitidx)
return bitdata
def mk_grid():
'''Load tilegrid, flattening all blocks into one dictionary'''
with open("%s/%s/tilegrid.json" % (os.getenv("XRAY_DATABASE_DIR"),
os.getenv("XRAY_DATABASE")), "r") as f:
new_grid = json.load(f)
# TODO: Migrate to new tilegrid format via library.
grid = {'tiles': new_grid, 'segments': {}}
for tile_name, tile in grid['tiles'].items():
bits = tile.get('bits', None)
if not bits:
continue
for block_name, block in bits.items():
segname = mksegment(tile_name, block_name)
grid['segments'][segname] = {
'baseaddr': [
block['baseaddr'],
block['offset'],
],
'type': tile['type'],
'frames': block['frames'],
'words': block['words'],
'tile_name': tile_name,
'block_name': block_name,
}
return grid
def mksegment(tile_name, block_name):
'''Create a segment name'''
return '%s:%s' % (tile_name, block_name)
def tile_segnames(grid):
ret = []
for tile_name, tile in grid['tiles'].items():
for block_name in tile['bits'].keys():
ret.append(mksegment(tile_name, block_name))
return ret
def run(bits_file, segnames, verbose=False):
grid = mk_grid()
bitdata = load_bitdata(bits_file)
# Default: print all
if segnames:
for i, segname in enumerate(segnames):
# Default to common tile config area if tile given without explicit block
if ':' not in segname:
segnames[i] = mksegment(segname, 'CLB_IO_CLK')
else:
segnames = sorted(tile_segnames(grid))
comment('Segments: %u' % len(segnames))
# XXX: previously this was sorted by address, not name
# revisit?
for segname in segnames:
handle_segment(segname, grid, bitdata, verbose=verbose)
print(
fasm.fasm_tuple_to_string(
disassembler.find_features_in_bitstream(bitdata, verbose=verbose),
canonical=canonical))
def main():
import argparse
# XXX: tool still works, but not well
# need to eliminate segments entirely
parser = argparse.ArgumentParser(
description='XXX: does not print all data?')
description='Convert 7-series bits file to FASM.')
parser.add_argument('--verbose', action='store_true', help='')
database_dir = os.getenv("XRAY_DATABASE_DIR")
database = os.getenv("XRAY_DATABASE")
db_root_kwargs = {}
if database_dir is None or database is None:
db_root_kwargs['required'] = True
else:
db_root_kwargs['required'] = False
db_root_kwargs['default'] = os.path.join(database_dir, database)
parser.add_argument('--db_root', help="Database root.", **db_root_kwargs)
parser.add_argument('bits_file', help='')
parser.add_argument(
'segnames', nargs='*', help='List of tile or tile:block to print')
'--verbose',
help='Print lines for unknown tiles and bits',
action='store_true')
parser.add_argument(
'--canonical', help='Output canonical bitstream.', action='store_true')
args = parser.parse_args()
run(args.bits_file, args.segnames, verbose=args.verbose)
run(args.db_root, args.bits_file, args.verbose, args.canonical)
if __name__ == '__main__':

View File

@ -4,7 +4,7 @@ while [ -h "$XRAY_ENV_PATH" ]; do # resolve $XRAY_ENV_PATH until the file is no
XRAY_ENV_PATH="$(readlink "$XRAY_ENV_PATH")"
[[ $XRAY_ENV_PATH != /* ]] && XRAY_ENV_PATH="$XRAY_UTILS_DIR/$XRAY_ENV_PATH" # if $XRAY_ENV_PATH was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
export PYTHONPATH="${XRAY_DIR}:$PYTHONPATH"
export PYTHONPATH="${XRAY_DIR}:${XRAY_DIR}/third_party/fasm:$PYTHONPATH"
export XRAY_UTILS_DIR="$( cd -P "$( dirname "$XRAY_ENV_PATH" )" && pwd )"
export XRAY_DIR="$( dirname "$XRAY_UTILS_DIR" )"

View File

@ -1,101 +1,11 @@
#!/usr/bin/env python3
from __future__ import print_function
from prjxray import fasm_assembler
from prjxray import db
import argparse
import os
import re
import sys
import json
import collections
class FASMSyntaxError(Exception):
pass
def parsebit(val):
'''Return "!012_23" => (12, 23, False)'''
isset = True
# Default is 0. Skip explicit call outs
if val[0] == '!':
isset = False
val = val[1:]
# 28_05 => 28, 05
seg_word_column, word_bit_n = val.split('_')
return int(seg_word_column), int(word_bit_n), isset
# TODO: migrate to library
def process_db(tile_type, process):
fns = [
# sites
"%s/%s/segbits_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type.lower()),
# interconnect
"%s/%s/segbits_int_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type[-1].lower()),
]
for fn in fns:
if os.path.exists(fn):
with open(fn, "r") as f:
for line in f:
process(line)
'''
Loosely based on segprint function
Maybe better to return as two distinct dictionaries?
{
'tile.meh': {
'O5': [(11, 2, False), (12, 2, True)],
'O6': [(11, 2, True), (12, 2, False)],
},
}
'''
segbitsdb = dict()
def get_database(tile_type):
if tile_type in segbitsdb:
return segbitsdb[tile_type]
segbitsdb[tile_type] = {}
def process(l):
# CLBLM_L.SLICEL_X1.ALUT.INIT[10] 29_14
parts = l.strip().split()
name = parts[0]
bit_vals = parts[1:]
# Assumption
# only 1 bit => non-enumerated value
if len(bit_vals) == 1:
seg_word_column, word_bit_n, isset = parsebit(bit_vals[0])
if not isset:
raise Exception(
"Expect single bit DB entries to be set, got %s" % l)
# Treat like an enumerated value with keys 0 or 1
assert name not in segbitsdb[tile_type]
segbitsdb[tile_type][name] = {
'0': [(seg_word_column, word_bit_n, 0)],
'1': [(seg_word_column, word_bit_n, 1)],
}
else:
# An enumerated value
# Split the base name and selected key
m = re.match(r'(.+)[.](.+)', name)
namepart = m.group(1)
key = m.group(2)
# May or may not be the first key encountered
bits_map = segbitsdb[tile_type].setdefault(namepart, {})
bits_map[key] = [parsebit(x) for x in bit_vals]
process_db(tile_type, process)
return segbitsdb[tile_type]
import os.path
def dump_frames_verbose(frames):
@ -135,214 +45,38 @@ def dump_frm(f, frames):
'0x%08X ' % addr + ','.join(['0x%08X' % w for w in words]) + '\n')
def mksegment(tile_name, block_name):
'''Create a segment name'''
return '%s:%s' % (tile_name, block_name)
def mk_grid():
'''Load tilegrid, flattening all blocks into one dictionary'''
with open("%s/%s/tilegrid.json" % (os.getenv("XRAY_DATABASE_DIR"),
os.getenv("XRAY_DATABASE")), "r") as f:
new_grid = json.load(f)
# TODO: Migrate to new tilegrid format via library.
grid = {'tiles': new_grid, 'segments': {}}
for tile_name, tile in grid['tiles'].items():
for block_name, block in tile['bits'].items():
segname = mksegment(tile_name, block_name)
grid['segments'][segname] = {
'baseaddr': [
block['baseaddr'],
block['offset'],
],
'type': tile['type'],
'frames': block['frames'],
'words': block['words'],
'tile_name': tile_name,
'block_name': block_name,
}
return grid
def frame_init(frames, addr):
'''Set given frame to 0'''
if not addr in frames:
frames[addr] = [0 for _i in range(101)]
def frames_init(frames, grid):
'''Set all frames to 0'''
for segj in grid['segments'].values():
seg_baseaddr, seg_word_base = segj['baseaddr']
seg_baseaddr = int(seg_baseaddr, 0)
for coli in range(segj['frames']):
frame_init(frames, seg_baseaddr + coli)
def frame_set(frames, frame_addr, word_addr, bit_index):
'''Set given bit in given frame address and word'''
frames[frame_addr][word_addr] |= 1 << bit_index
def frame_clear(frames, frame_addr, word_addr, bit_index):
'''Set given bit in given frame address and word'''
frames[frame_addr][word_addr] &= 0xFFFFFFFF ^ (1 << bit_index)
def parse_line(l):
# Comment
# Remove all text including and after #
i = l.rfind('#')
if i >= 0:
l = l[0:i]
l = l.strip()
# Ignore blank lines
if not l:
return
# tile.site.stuff value
# INT_L_X10Y102.CENTER_INTER_L.IMUX_L1 EE2END0
# Optional value
m = re.match(r'([a-zA-Z0-9_]+)[.]([a-zA-Z0-9_.\[\]]+)([ ](.+))?', l)
if not m:
raise FASMSyntaxError("Bad line: %s" % l)
tile = m.group(1)
name = m.group(2)
value = m.group(4)
return tile, name, value
def check_duplicate(used_names, tile, name, l, line_number):
'''Throw an exception if a conflicting FASM directive was given'''
used_name = (tile, name)
old_line_number = used_names.get(used_name, None)
if old_line_number:
raise FASMSyntaxError(
"Duplicate name lines %d and %d, second line: %s" %
(old_line_number, line_number, l))
used_names[used_name] = line_number
def update_segbit(
frames, seg_word_column, word_bit_n, isset, seg_baseaddr,
seg_word_base):
'''Set or clear a single bit in a segment at the given word column and word bit position'''
# Now we have the word column and word bit index
# Combine with the segments relative frame position to fully get the position
frame_addr = seg_baseaddr + seg_word_column
# 2 words per segment
word_addr = seg_word_base + word_bit_n // 32
bit_index = word_bit_n % 32
if isset:
frame_set(frames, frame_addr, word_addr, bit_index)
else:
frame_clear(frames, frame_addr, word_addr, bit_index)
def default_value(db_vals, name):
# If its binary, allow omitted value default to 1
if tuple(sorted(db_vals.keys())) == ('0', '1'):
return '1'
else:
raise FASMSyntaxError(
"Enumerable entry %s must have explicit value" % name)
def process_line(line_number, l, grid, frames, used_names):
parsed = parse_line(l)
# empty line
if not parsed:
return
tile_name, name, value = parsed
check_duplicate(used_names, tile_name, name, l, line_number)
tilej = grid['tiles'][tile_name]
for block_name, block in tilej['bits'].items():
segname = mksegment(tile_name, block_name)
segj = grid['segments'][segname]
seg_baseaddr, seg_word_base = segj['baseaddr']
seg_baseaddr = int(seg_baseaddr, 0)
# Ensure that all frames exist for this segment
# FIXME: type dependent
for coli in range(segj['frames']):
frame_init(frames, seg_baseaddr + coli)
# Now lets look up the bits we need frames for
segdb = get_database(segj['type'])
db_k = '%s.%s' % (tilej['type'], name)
try:
db_vals = segdb[db_k]
except KeyError:
raise FASMSyntaxError(
"Segment DB %s, key %s not found from line '%s'" %
(segj['type'], db_k, l)) from None
if not value:
value = default_value(db_vals, name)
# Get the specific entry we need
try:
db_vals = db_vals[value]
except KeyError:
raise FASMSyntaxError(
"Invalid entry %s. Valid entries are %s" %
(value, db_vals.keys()))
for seg_word_column, word_bit_n, isset in db_vals:
update_segbit(
frames, seg_word_column, word_bit_n, isset, seg_baseaddr,
seg_word_base)
def run(f_in, f_out, sparse=False, debug=False):
# address to array of 101 32 bit words
frames = {}
# Directives we've seen so far
# Complain if there is a duplicate
# Contains line number of last entry
used_names = {}
grid = mk_grid()
if not sparse:
# Initiaize bitstream to 0
frames_init(frames, grid)
for line_number, l in enumerate(f_in, 1):
process_line(line_number, l, grid, frames, used_names)
def run(db_root, filename_in, f_out, sparse=False, debug=False):
assembler = fasm_assembler.FasmAssembler(db.Database(db_root))
assembler.parse_fasm_filename(filename_in)
frames = assembler.get_frames(sparse=sparse)
if debug:
#dump_frames_verbose(frames)
dump_frames_sparse(frames)
dump_frm(f_out, frames)
if __name__ == '__main__':
import argparse
def main():
parser = argparse.ArgumentParser(
description=
'Convert FPGA configuration description ("FPGA assembly") into binary frame equivalent'
)
database_dir = os.getenv("XRAY_DATABASE_DIR")
database = os.getenv("XRAY_DATABASE")
db_root_kwargs = {}
if database_dir is None or database is None:
db_root_kwargs['required'] = True
else:
db_root_kwargs['required'] = False
db_root_kwargs['default'] = os.path.join(database_dir, database)
parser.add_argument('--db_root', help="Database root.", **db_root_kwargs)
parser.add_argument(
'--sparse', action='store_true', help="Don't zero fill all frames")
parser.add_argument(
'--debug', action='store_true', help="Print debug dump")
parser.add_argument(
'fn_in',
default='/dev/stdin',
nargs='?',
help='Input FPGA assembly (.fasm) file')
parser.add_argument('fn_in', help='Input FPGA assembly (.fasm) file')
parser.add_argument(
'fn_out',
default='/dev/stdout',
@ -351,7 +85,12 @@ if __name__ == '__main__':
args = parser.parse_args()
run(
open(args.fn_in, 'r'),
open(args.fn_out, 'w'),
db_root=args.db_root,
filename_in=args.fn_in,
f_out=open(args.fn_out, 'w'),
sparse=args.sparse,
debug=args.debug)
if __name__ == '__main__':
main()

57
utils/fasm2pips.py Normal file
View File

@ -0,0 +1,57 @@
#!/usr/bin/env python3
""" Tool for generating Vivavo commands to highlight objects from a FASM file.
Currently this tool only highlights pips directly referenced in the FASM file.
"""
from __future__ import print_function
import os.path
import fasm
from prjxray import db
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Outputs a Vivavo highlight_objects command from a FASM file.')
database_dir = os.getenv("XRAY_DATABASE_DIR")
database = os.getenv("XRAY_DATABASE")
db_root_kwargs = {}
if database_dir is None or database is None:
db_root_kwargs['required'] = True
else:
db_root_kwargs['required'] = False
db_root_kwargs['default'] = os.path.join(database_dir, database)
parser.add_argument('--db_root', help="Database root.", **db_root_kwargs)
parser.add_argument('fn_in', help='Input FPGA assembly (.fasm) file')
args = parser.parse_args()
database = db.Database(args.db_root)
grid = database.grid()
def inner():
for line in fasm.parse_fasm_filename(args.fn_in):
if not line.set_feature:
continue
parts = line.set_feature.feature.split('.')
tile = parts[0]
gridinfo = grid.gridinfo_at_tilename(tile)
tile_type = database.get_tile_type(gridinfo.tile_type)
for pip in tile_type.pips:
if pip.net_from == parts[2] and pip.net_to == parts[1]:
yield '{}/{}.{}'.format(tile, gridinfo.tile_type, pip.name)
print(
'highlight_objects [concat {}]'.format(
' '.join('[get_pips {}]'.format(pip) for pip in inner())))
if __name__ == '__main__':
main()

89
utils/fasm_pprint.py Normal file
View File

@ -0,0 +1,89 @@
#!/usr/bin/env python3
'''
Pretty print FASM.
Sanity checks FASM against prjxray database.
Can output canonical FASM.
In the future may support other formatting options.
'''
import os
import fasm
from prjxray import db
def process_fasm(db_root, fasm_file, canonical):
database = db.Database(db_root)
grid = database.grid()
for fasm_line in fasm.parse_fasm_filename(fasm_file):
if not fasm_line.set_feature:
if not canonical:
yield fasm_line
for feature in fasm.canonical_features(fasm_line.set_feature):
parts = feature.feature.split('.')
tile = parts[0]
gridinfo = grid.gridinfo_at_tilename(tile)
tile_segbits = database.get_tile_segbits(gridinfo.tile_type)
address = 0
if feature.start is not None:
address = feature.start
feature_name = '{}.{}'.format(
gridinfo.tile_type, '.'.join(parts[1:]))
# Convert feature to bits. If no bits are set, feature is
# psuedo pip, and should not be output from canonical FASM.
bits = tuple(
tile_segbits.feature_to_bits(feature_name, address=address))
if len(bits) == 0 and canonical:
continue
# In canonical output, only output the canonical features.
if canonical:
yield fasm.FasmLine(
set_feature=feature,
annotations=None,
comment=None,
)
# If not in canonical mode, output original FASM line
if not canonical:
yield fasm_line
def run(db_root, fasm_file, canonical):
print(
fasm.fasm_tuple_to_string(
process_fasm(db_root, fasm_file, canonical), canonical=canonical))
def main():
import argparse
parser = argparse.ArgumentParser(description='Pretty print a FASM file.')
database_dir = os.getenv("XRAY_DATABASE_DIR")
database = os.getenv("XRAY_DATABASE")
db_root_kwargs = {}
if database_dir is None or database is None:
db_root_kwargs['required'] = True
else:
db_root_kwargs['required'] = False
db_root_kwargs['default'] = os.path.join(database_dir, database)
parser.add_argument('--db_root', help="Database root.", **db_root_kwargs)
parser.add_argument('fasm_file', help='Input FASM file')
parser.add_argument(
'--canonical', help='Output canonical bitstream.', action='store_true')
args = parser.parse_args()
run(args.db_root, args.fasm_file, args.canonical)
if __name__ == '__main__':
main()

View File

@ -236,6 +236,9 @@ def mksegment(tile_name, block_name):
def tile_segnames(grid):
ret = []
for tile_name, tile in grid['tiles'].items():
if 'bits' not in tile:
continue
for block_name in tile['bits'].keys():
ret.append(mksegment(tile_name, block_name))
return ret