blob: f90cf1e0b12ebc107b3a86b0d4e774cecabd349e [file] [log] [blame]
#!/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
import sys
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 word_addr >= 101:
print(f"get_frames: invalid word address {word_addr} in line: {self.frames_line[(frame_addr, word_addr, bit_index)]}", file=sys.stderr)
if frame_addr not in frames:
print(f"get_frames: invalid frame address {frame_addr:x8}", file=sys.stderr)
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 word_addr >= 101:
print(f"frame_set: invalid word address {word_addr} in line: {line}", file=sys.stderr)
return
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 word_addr >= 101:
print(f"frame_clear: invalid word address {word_addr} in line: {line}", file=sys.stderr)
return
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)