mirror of https://github.com/openXC7/prjxray.git
201 lines
6.5 KiB
Python
201 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright (C) 2017-2020 The Project X-Ray Authors.
|
|
#
|
|
# Use of this source code is governed by a ISC-style
|
|
# license that can be found in the LICENSE file or at
|
|
# https://opensource.org/licenses/ISC
|
|
#
|
|
# SPDX-License-Identifier: ISC
|
|
import fasm
|
|
from prjxray import bitstream
|
|
|
|
|
|
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.seen_tile = set()
|
|
self.frames_in_use = set()
|
|
|
|
self.frames = {}
|
|
self.frames_line = {}
|
|
|
|
self.feature_callback = lambda feature: None
|
|
|
|
def set_feature_callback(self, feature_callback):
|
|
self.feature_callback = feature_callback
|
|
|
|
def get_frames(self, sparse=False):
|
|
if not sparse:
|
|
frames = self.frames_init()
|
|
else:
|
|
# Even in sparse mode, zero all frames for any tile that is
|
|
# setting a bit. This handles the case where the tile has
|
|
# multiple frames, but the FASM only specifies some of the frames.
|
|
frames = {}
|
|
for frame in self.frames_in_use:
|
|
init_frame_at_address(frames, frame)
|
|
|
|
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)
|
|
|
|
def update_segbit(bit):
|
|
'''Set or clear a single bit in a segment at the given word column and word bit position'''
|
|
|
|
frame_addr = bit.word_column
|
|
word_addr = 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.grid.get_tile_segbits_at_tilename(tile)
|
|
|
|
self.seen_tile.add(tile)
|
|
|
|
db_k = '%s.%s' % (gridinfo.tile_type, feature)
|
|
|
|
any_bits = set()
|
|
|
|
try:
|
|
for block_type, bit in segbits.feature_to_bits(gridinfo.bits, db_k,
|
|
address):
|
|
any_bits.add(block_type)
|
|
update_segbit(bit)
|
|
except KeyError:
|
|
raise FasmLookupError(
|
|
"Segment DB %s, key %s not found from line '%s'" %
|
|
(gridinfo.tile_type, db_k, line))
|
|
|
|
for block_type in any_bits:
|
|
# Mark all frames used by this tile as in use.
|
|
bits = gridinfo.bits[block_type]
|
|
for frame in range(bits.base_address,
|
|
bits.base_address + bits.frames):
|
|
self.frames_in_use.add(frame)
|
|
|
|
def add_fasm_line(self, line, missing_features):
|
|
if not line.set_feature:
|
|
return
|
|
|
|
self.feature_callback(line.set_feature)
|
|
|
|
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
|
|
|
|
try:
|
|
self.enable_feature(tile, feature, address, line_str)
|
|
except FasmLookupError as e:
|
|
missing_features.append(str(e))
|
|
|
|
def parse_fasm_filename(self, filename, extra_features=[]):
|
|
missing_features = []
|
|
for line in fasm.parse_fasm_filename(filename):
|
|
self.add_fasm_line(line, missing_features)
|
|
|
|
for line in extra_features:
|
|
self.add_fasm_line(line, missing_features)
|
|
|
|
if missing_features:
|
|
raise FasmLookupError('\n'.join(missing_features))
|
|
|
|
def mark_roi_frames(self, roi):
|
|
for tile in roi.gen_tiles():
|
|
gridinfo = self.grid.gridinfo_at_tilename(tile)
|
|
|
|
for block_type in gridinfo.bits:
|
|
bits = gridinfo.bits[block_type]
|
|
for frame in range(bits.base_address,
|
|
bits.base_address + bits.frames):
|
|
self.frames_in_use.add(frame)
|