text: #!/usr/bin/env python3
  # -*- coding: utf-8 -*-
  #
  # Copyright 2020-2022 F4PGA Authors
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  # SPDX-License-Identifier: Apache-2.0
  
  import re
  import fasm
  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,
                                block_type,
                                bits,
                                solved_bitdata,
                                bitdata,
                                verbose=False):
          gridinfo = self.grid.gridinfo_at_tilename(tile_name)
  
          try:
              tile_segbits = self.grid.get_tile_segbits_at_tilename(tile_name)
          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(
                  block_type, bits, bitdata):
              for frame, bit in ones_matched:
                  if frame not in solved_bitdata:
                      solved_bitdata[frame] = set()
                  solved_bitdata[frame].add(bit)
  
              yield mk_fasm(tile_name=tile_name, feature=feature)
  
      def find_features_in_bitstream(self, bitdata, verbose=False):
          solved_bitdata = {}
          frames = set(bitdata.keys())
          tiles_checked = set()
  
          emitted_features = 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, bits_info.block_type) 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, bits_info.block_type))
  
                  for fasm_line in self.find_features_in_tile(
                          bits_info.tile,
                          bits_info.block_type,
                          bits_info.bits,
                          solved_bitdata,
                          bitdata,
                          verbose=verbose):
                      if fasm_line not in emitted_features:
                          emitted_features.add(fasm_line)
                          yield fasm_line
  
              remaining_bits = bitdata[frame][1]
              if frame in solved_bitdata:
                  remaining_bits -= solved_bitdata[frame]
  
              if len(remaining_bits) > 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(remaining_bits),
                      ))
  
                  for bit in remaining_bits:
                      frame_offset = frame % bitstream.FRAME_ALIGNMENT
                      aligned_frame = frame - frame_offset
                      wordidx = bit // bitstream.WORD_SIZE_BITS
                      bitidx = bit % bitstream.WORD_SIZE_BITS
  
                      annotations = []
                      annotations.append(
                          fasm.Annotation(
                              'unknown_bit', '{:08x}_{}_{}'.format(
                                  frame, wordidx, bitidx)))
                      annotations.append(
                          fasm.Annotation('unknown_segment',
                                          '0x{:08x}'.format(aligned_frame)))
                      annotations.append(
                          fasm.Annotation(
                              'unknown_segbit', '{:02d}_{:02d}'.format(
                                  frame_offset, bit)))
                      yield fasm.FasmLine(
                          set_feature=None,
                          annotations=tuple(annotations),
                          comment=None,
                      )
  
      def is_zero_feature(self, feature):
          parts = feature.split('.')
          tile = parts[0]
          gridinfo = self.grid.gridinfo_at_tilename(tile)
          feature = '.'.join(parts[1:])
  
          db_k = '%s.%s' % (gridinfo.tile_type, feature)
          segbits = self.grid.get_tile_segbits_at_tilename(tile)
          any_bits = False
          for block_type, bit in segbits.feature_to_bits(gridinfo.bits, db_k):
              if bit.isset:
                  any_bits = True
                  break
  
          return not any_bits
  