text: #!/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 copy
  import json
  import os
  from utils import xjson
  '''
  Historically we grouped data into "segments"
  These were a region of the bitstream that encoded one or more tiles
  However, this didn't scale with certain tiles like BRAM
  Some sites had multiple bitstream areas and also occupied multiple tiles
  
  Decoding was then shifted to instead describe how each title is encoded
  A post processing step verifies that two tiles don't reference the same bitstream area
  '''
  
  import util as localutil
  
  
  def nolr(tile_type):
      '''
      Remove _L or _R suffix tile_type suffix, if present
      Ex: BRAM_INT_INTERFACE_L => BRAM_INT_INTERFACE
      Ex: VBRK => VBRK
      '''
      postfix = tile_type[-2:]
      if postfix in ('_L', '_R'):
          return tile_type[:-2]
      else:
          return tile_type
  
  
  def make_tiles_by_grid(database):
      # lookup tile names by (X, Y)
      tiles_by_grid = dict()
  
      for tile_name in database:
          tile = database[tile_name]
          tiles_by_grid[(tile["grid_x"], tile["grid_y"])] = tile_name
  
      return tiles_by_grid
  
  
  def propagate_INT_lr_bits(
          database, tiles_by_grid, tile_frames_map, verbose=False):
      '''Populate segment base addresses: L/R along INT column'''
  
      int_frames, int_words, _ = localutil.get_entry('INT', 'CLB_IO_CLK')
  
      verbose and print('')
      for tile in database:
          if database[tile]["type"] not in ["INT_L", "INT_R"]:
              continue
  
          if not database[tile]["bits"]:
              continue
  
          grid_x = database[tile]["grid_x"]
          grid_y = database[tile]["grid_y"]
          baseaddr = int(database[tile]["bits"]["CLB_IO_CLK"]["baseaddr"], 0)
          offset = database[tile]["bits"]["CLB_IO_CLK"]["offset"]
  
          if database[tile]["type"] == "INT_L":
              grid_x += 1
              baseaddr = baseaddr + 0x80
          elif database[tile]["type"] == "INT_R":
              grid_x -= 1
              baseaddr = baseaddr - 0x80
          else:
              assert 0, database[tile]["type"]
  
          # ROI at edge?
          if (grid_x, grid_y) not in tiles_by_grid:
              verbose and print('  Skip edge')
              continue
  
          other_tile = tiles_by_grid[(grid_x, grid_y)]
  
          if database[tile]["type"] == "INT_L":
              assert database[other_tile]["type"] == "INT_R"
          elif database[tile]["type"] == "INT_R":
              assert database[other_tile]["type"] == "INT_L"
          else:
              assert 0
  
          localutil.add_tile_bits(
              other_tile, database[other_tile], baseaddr, offset, int_frames,
              int_words, tile_frames_map)
  
  
  def propagate_INT_bits_in_column(database, tiles_by_grid, tile_frames_map):
      """ Propigate INT offsets up and down INT columns.
  
      INT columns appear to be fairly regular, where starting from offset 0,
      INT tiles next to INT tiles increase the word offset by 2.  The HCLK tile
      is surrounded above and sometimes below by an INT tile.  Because the HCLK
      tile only useds one word, the offset increase by one at the HCLK.
  
      """
  
      seen_int = set()
  
      int_frames, int_words, _ = localutil.get_entry('INT', 'CLB_IO_CLK')
      hclk_frames, hclk_words, _ = localutil.get_entry('HCLK', 'CLB_IO_CLK')
  
      for tile_name in sorted(database.keys()):
          tile = database[tile_name]
  
          if tile['type'] not in ['INT_L', 'INT_R']:
              continue
  
          l_or_r = tile['type'][-1]
  
          if not tile['bits']:
              continue
  
          if tile_name in seen_int:
              continue
  
          # Walk down INT column
          while True:
              seen_int.add(tile_name)
  
              next_tile = tiles_by_grid[(tile['grid_x'], tile['grid_y'] + 1)]
              next_tile_type = database[next_tile]['type']
  
              if tile['bits']['CLB_IO_CLK']['offset'] == 0:
                  assert next_tile_type in [
                      'B_TERM_INT', 'BRKH_INT', 'BRKH_B_TERM_INT'
                  ], next_tile_type
                  break
  
              baseaddr = int(tile['bits']['CLB_IO_CLK']['baseaddr'], 0)
              offset = tile['bits']['CLB_IO_CLK']['offset']
  
              if tile['type'].startswith(
                      'INT_') and next_tile_type == tile['type']:
                  # INT next to INT
                  offset -= int_words
                  localutil.add_tile_bits(
                      next_tile, database[next_tile], baseaddr, offset,
                      int_frames, int_words, tile_frames_map)
              elif tile['type'].startswith('INT_'):
                  # INT above HCLK
                  assert next_tile_type.startswith(
                      'HCLK_{}'.format(l_or_r)), next_tile_type
  
                  offset -= hclk_words
                  localutil.add_tile_bits(
                      next_tile, database[next_tile], baseaddr, offset,
                      hclk_frames, hclk_words, tile_frames_map)
              else:
                  # HCLK above INT
                  assert tile['type'].startswith(
                      'HCLK_{}'.format(l_or_r)), tile['type']
                  if next_tile_type == 'INT_{}'.format(l_or_r):
                      offset -= int_words
                      localutil.add_tile_bits(
                          next_tile, database[next_tile], baseaddr, offset,
                          int_frames, int_words, tile_frames_map)
                  else:
                      # Handle special case column where the PCIE tile is present.
                      assert next_tile_type in ['PCIE_NULL'], next_tile_type
                      break
  
              tile_name = next_tile
              tile = database[tile_name]
  
          # Walk up INT column
          while True:
              seen_int.add(tile_name)
  
              next_tile = tiles_by_grid[(tile['grid_x'], tile['grid_y'] - 1)]
              next_tile_type = database[next_tile]['type']
  
              if tile['bits']['CLB_IO_CLK']['offset'] == 99:
                  assert next_tile_type in [
                      'T_TERM_INT', 'BRKH_INT', 'BRKH_TERM_INT', 'BRKH_INT_PSS'
                  ], next_tile_type
                  break
  
              baseaddr = int(tile['bits']['CLB_IO_CLK']['baseaddr'], 0)
              offset = tile['bits']['CLB_IO_CLK']['offset']
  
              if tile['type'].startswith(
                      'INT_') and next_tile_type == tile['type']:
                  # INT next to INT
                  offset += int_words
                  localutil.add_tile_bits(
                      next_tile, database[next_tile], baseaddr, offset,
                      int_frames, int_words, tile_frames_map)
              elif tile['type'].startswith('INT_'):
                  # INT below HCLK
                  assert next_tile_type.startswith(
                      'HCLK_{}'.format(l_or_r)), next_tile_type
  
                  offset += int_words
                  localutil.add_tile_bits(
                      next_tile, database[next_tile], baseaddr, offset,
                      hclk_frames, hclk_words, tile_frames_map)
              else:
                  # HCLK below INT
                  assert tile['type'].startswith(
                      'HCLK_{}'.format(l_or_r)), tile['type']
                  assert next_tile_type == 'INT_{}'.format(
                      l_or_r), next_tile_type
  
                  offset += hclk_words
                  localutil.add_tile_bits(
                      next_tile, database[next_tile], baseaddr, offset,
                      int_frames, int_words, tile_frames_map)
  
              tile_name = next_tile
              tile = database[tile_name]
  
  
  def propagate_INT_INTERFACE_bits_in_column(
          database, tiles_by_grid, int_interface_name, tile_frames_map):
      """ Propagate INT_INTERFACE column for a given INT_INTERFACE tile name.
  
      INT_INTERFACE tiles do not usually have any PIPs or baseaddresses,
      except for a few cases such as PCIE or GTP INTERFACE tiles.
  
      These are very regular tiles, except for the horizontal clock line,
      which adds a one-word offset.
  
      This function replicates the baseaddress and calculates the correct offset
      for each INT INTERFACE tile in a column, starting from a tile in the column
      that has the baseaddress calculated from the corresponding tilegrid fuzzer.
      """
  
      seen_int = set()
  
      int_frames, int_words, _ = localutil.get_entry('INT', 'CLB_IO_CLK')
      hclk_frames, hclk_words, _ = localutil.get_entry('HCLK', 'CLB_IO_CLK')
  
      for tile_name in sorted(database.keys()):
          tile = database[tile_name]
  
          if not tile['type'].startswith(int_interface_name):
              continue
  
          if not tile['bits']:
              continue
  
          if tile_name in seen_int:
              continue
  
          # Walk down INT column
          down_tile = tile
          down_tile_name = tile_name
          while True:
              seen_int.add(down_tile_name)
  
              baseaddr = int(down_tile['bits']['CLB_IO_CLK']['baseaddr'], 0)
              offset = down_tile['bits']['CLB_IO_CLK']['offset']
              extra_offset = 0
  
              next_tile = tiles_by_grid[(
                  down_tile['grid_x'], down_tile['grid_y'] + 1)]
              if next_tile.startswith("HCLK"):
                  next_tile = tiles_by_grid[(
                      down_tile['grid_x'], down_tile['grid_y'] + 2)]
                  extra_offset = hclk_words
  
              next_tile_type = database[next_tile]['type']
  
              if next_tile_type != tile['type']:
                  break
  
              if next_tile_type == down_tile['type']:
                  # INT next to INT
                  offset -= (int_words + extra_offset)
                  localutil.add_tile_bits(
                      next_tile, database[next_tile], baseaddr, offset,
                      int_frames, int_words, tile_frames_map)
  
              down_tile_name = next_tile
              down_tile = database[down_tile_name]
  
          # Walk up INT column
          up_tile = tile
          up_tile_name = tile_name
          while True:
              seen_int.add(up_tile_name)
  
              baseaddr = int(up_tile['bits']['CLB_IO_CLK']['baseaddr'], 0)
              offset = up_tile['bits']['CLB_IO_CLK']['offset']
              extra_offset = 0
  
              next_tile = tiles_by_grid[(
                  up_tile['grid_x'], up_tile['grid_y'] - 1)]
              if next_tile.startswith("HCLK"):
                  next_tile = tiles_by_grid[(
                      up_tile['grid_x'], up_tile['grid_y'] - 2)]
                  extra_offset = hclk_words
  
              next_tile_type = database[next_tile]['type']
  
              if next_tile_type != tile['type']:
                  break
  
              if next_tile_type == up_tile['type']:
                  # INT next to INT
                  offset += (int_words + extra_offset)
                  localutil.add_tile_bits(
                      next_tile, database[next_tile], baseaddr, offset,
                      int_frames, int_words, tile_frames_map)
  
              up_tile_name = next_tile
              up_tile = database[up_tile_name]
  
  
  def propagate_rebuf(database, tiles_by_grid):
      """ Writing a fuzzer for the CLK_BUFG_REBUF tiles is hard, so propigate from CLK_HROW tiles.
  
      In the clock column, there is a CLK_BUFG_REBUF above and below the CLK_HROW
      tile.  Each clock column appears to use the same offsets, so propigate
      the base address and frame count, and update the offset and word count.
  
      """
      for tile_name in sorted(database.keys()):
          tile = database[tile_name]
  
          if tile['type'] not in ['CLK_HROW_BOT_R', 'CLK_HROW_TOP_R']:
              continue
  
          rebuf_below = tiles_by_grid[(tile['grid_x'], tile['grid_y'] - 12)]
          assert database[rebuf_below]['type'] == 'CLK_BUFG_REBUF', database[
              rebuf_below]['type']
          rebuf_above = tiles_by_grid[(tile['grid_x'], tile['grid_y'] + 13)]
          assert database[rebuf_above]['type'] == 'CLK_BUFG_REBUF', database[
              rebuf_below]['type']
  
          assert database[tile_name]['bits']['CLB_IO_CLK'][
              'offset'] == 42, database[tile_name]['bits']['CLB_IO_CLK']
          database[rebuf_below]['bits'] = copy.deepcopy(
              database[tile_name]['bits'])
          database[rebuf_below]['bits']['CLB_IO_CLK']['offset'] = 73
          database[rebuf_below]['bits']['CLB_IO_CLK']['words'] = 4
  
          database[rebuf_above]['bits'] = copy.deepcopy(
              database[tile_name]['bits'])
          database[rebuf_above]['bits']['CLB_IO_CLK']['offset'] = 24
          database[rebuf_above]['bits']['CLB_IO_CLK']['words'] = 4
  
  
  def propagate_IOB_SING(database, tiles_by_grid):
      """ The IOB_SING are half tiles at top and bottom of every IO column.
  
      Unlike most tiles, they do not behave consistently.  The tile at the top
      of the column is the bottom half of a full IOB, and the tile at the bottom
      of the column is the top half of a full IOB.  For this reason, explicit
      bit aliasing is used to map the full IOB bits into the two halves, and a
      mapping is provided for the site naming.
  
      """
  
      seen_iobs = set()
      for tile in database:
          if tile in seen_iobs:
              continue
  
          if database[tile]["type"] not in ["LIOB33", "RIOB33"]:
              continue
  
          while True:
              prev_tile = tile
              tile = tiles_by_grid[(
                  database[tile]['grid_x'], database[tile]['grid_y'] + 1)]
              if '_SING' in database[tile]['type']:
                  break
  
          bottom_tile = tile
          seen_iobs.add(bottom_tile)
  
          bits = database[prev_tile]['bits']['CLB_IO_CLK']
  
          while True:
              tile = tiles_by_grid[(
                  database[tile]['grid_x'], database[tile]['grid_y'] - 1)]
              seen_iobs.add(tile)
  
              if '_SING' in database[tile]['type']:
                  break
  
              if 'CLB_IO_CLK' in database[tile]['bits']:
                  assert bits['baseaddr'] == database[tile]['bits'][
                      'CLB_IO_CLK']['baseaddr']
                  assert bits['frames'] == database[tile]['bits']['CLB_IO_CLK'][
                      'frames']
                  assert bits['words'] == database[tile]['bits']['CLB_IO_CLK'][
                      'words']
  
          top_tile = tile
  
          database[top_tile]['bits']['CLB_IO_CLK'] = copy.deepcopy(bits)
          database[top_tile]['bits']['CLB_IO_CLK']['words'] = 2
          database[top_tile]['bits']['CLB_IO_CLK']['offset'] = 99
          database[top_tile]['bits']['CLB_IO_CLK']['alias'] = {
              'type': database[prev_tile]['type'],
              'start_offset': 0,
              'sites': {
                  'IOB33_Y0': 'IOB33_Y1',
              }
          }
  
          database[bottom_tile]['bits']['CLB_IO_CLK'] = copy.deepcopy(bits)
          database[bottom_tile]['bits']['CLB_IO_CLK']['words'] = 2
          database[bottom_tile]['bits']['CLB_IO_CLK']['offset'] = 0
          database[bottom_tile]['bits']['CLB_IO_CLK']['alias'] = {
              'type': database[prev_tile]['type'],
              'start_offset': 2,
              'sites': {
                  'IOB33_Y0': 'IOB33_Y0',
              }
          }
  
  
  def propagate_IOI_SING(database, tiles_by_grid):
      """
      The IOI_SING, similarly to IOB_SING, are half tiles at top and bottom of every
      IO column.
  
      The tile contains half of the sites that are present in the full IOI,
      namely one ILOGIC, OLOGIC and IDELAY.
      """
  
      seen_iois = set()
      for tile in database:
          if tile in seen_iois:
              continue
  
          if database[tile]["type"] not in ["LIOI3", "RIOI3"]:
              continue
  
          while True:
              prev_tile = tile
              tile = tiles_by_grid[(
                  database[tile]['grid_x'], database[tile]['grid_y'] + 1)]
              if '_SING' in database[tile]['type']:
                  break
  
          bottom_tile = tile
          seen_iois.add(bottom_tile)
  
          bits = database[prev_tile]['bits']['CLB_IO_CLK']
  
          while True:
              tile = tiles_by_grid[(
                  database[tile]['grid_x'], database[tile]['grid_y'] - 1)]
              seen_iois.add(tile)
  
              if '_SING' in database[tile]['type']:
                  break
  
              if 'CLB_IO_CLK' in database[tile]['bits']:
                  if tile.startswith("LIOI") or tile.startswith("RIOI"):
                      assert bits['baseaddr'] == database[tile]['bits'][
                          'CLB_IO_CLK']['baseaddr']
                      assert bits['frames'] == database[tile]['bits'][
                          'CLB_IO_CLK']['frames'], "{}:{} == {}".format(
                              tile, bits['frames'],
                              database[tile]['bits']['CLB_IO_CLK']['frames'])
                      assert bits['words'] == database[tile]['bits'][
                          'CLB_IO_CLK']['words'], "{}: {} != {}".format(
                              tile, bits['words'],
                              database[tile]['bits']['CLB_IO_CLK']['words'])
  
          top_tile = tile
  
          database[top_tile]['bits']['CLB_IO_CLK'] = copy.deepcopy(bits)
          database[top_tile]['bits']['CLB_IO_CLK']['words'] = 2
          database[top_tile]['bits']['CLB_IO_CLK']['offset'] = 99
          database[top_tile]['bits']['CLB_IO_CLK']['alias'] = {
              'type': database[prev_tile]['type'],
              'start_offset': 0,
              'sites': {}
          }
  
          database[bottom_tile]['bits']['CLB_IO_CLK'] = copy.deepcopy(bits)
          database[bottom_tile]['bits']['CLB_IO_CLK']['words'] = 2
          database[bottom_tile]['bits']['CLB_IO_CLK']['offset'] = 0
          database[bottom_tile]['bits']['CLB_IO_CLK']['alias'] = {
              'type': database[prev_tile]['type'],
              'start_offset': 2,
              'sites': {}
          }
  
  
  def propagate_IOI_Y9(database, tiles_by_grid):
      """
      There are IOI tiles (X0Y9 and X43Y9) that have the frame address 1 frame
      higher than the rest, just like for some of the SING tiles.
  
      """
      ioi_tiles = os.getenv('XRAY_IOI3_TILES')
  
      assert ioi_tiles is not None, "XRAY_IOI3_TILES env variable not set"
      tiles = ioi_tiles.split(" ")
  
      for tile in tiles:
          prev_tile = tiles_by_grid[(
              database[tile]['grid_x'], database[tile]['grid_y'] - 1)]
          while database[prev_tile]["type"] != database[tile]["type"]:
              prev_tile = tiles_by_grid[(
                  database[prev_tile]['grid_x'],
                  database[prev_tile]['grid_y'] - 1)]
          bits = database[prev_tile]['bits']['CLB_IO_CLK']
          database[tile]['bits']['CLB_IO_CLK'] = copy.deepcopy(bits)
          database[tile]['bits']['CLB_IO_CLK']['words'] = 4
          database[tile]['bits']['CLB_IO_CLK']['offset'] = 18
  
  
  def alias_HCLKs(database):
      """ Generate HCLK aliases for HCLK_[LR] subsets.
  
      There are some HCLK_[LR] tiles that are missing some routing due to
      obstructions, e.g. PCIE hardblock.  These tiles do not have southbound
      clock routing, but are otherwise the same as HCLK_[LR] tiles.
  
      Simply alias their segbits.
  
      """
      for tile in database:
          if database[tile]['type'] == "HCLK_L_BOT_UTURN":
              database[tile]['bits']['CLB_IO_CLK']['alias'] = {
                  "sites": {},
                  "start_offset": 0,
                  "type": "HCLK_L"
              }
          elif database[tile]['type'] == "HCLK_R_BOT_UTURN":
              database[tile]['bits']['CLB_IO_CLK']['alias'] = {
                  "sites": {},
                  "start_offset": 0,
                  "type": "HCLK_R"
              }
  
  
  def run(json_in_fn, json_out_fn, verbose=False):
      # Load input files
      database = json.load(open(json_in_fn, "r"))
      tiles_by_grid = make_tiles_by_grid(database)
  
      tile_frames_map = localutil.TileFrames()
      propagate_INT_lr_bits(
          database, tiles_by_grid, tile_frames_map, verbose=verbose)
      propagate_INT_bits_in_column(database, tiles_by_grid, tile_frames_map)
      propagate_INT_INTERFACE_bits_in_column(
          database, tiles_by_grid, "GTP_INT_INTERFACE", tile_frames_map)
      propagate_INT_INTERFACE_bits_in_column(
          database, tiles_by_grid, "PCIE_INT_INTERFACE", tile_frames_map)
      propagate_rebuf(database, tiles_by_grid)
      propagate_IOB_SING(database, tiles_by_grid)
      propagate_IOI_SING(database, tiles_by_grid)
      propagate_IOI_Y9(database, tiles_by_grid)
      alias_HCLKs(database)
  
      # Save
      xjson.pprint(open(json_out_fn, "w"), database)
  
  
  def main():
      import argparse
  
      parser = argparse.ArgumentParser(
          description="Generate tilegrid.json from bitstream deltas")
  
      parser.add_argument("--verbose", action="store_true", help="")
      parser.add_argument(
          "--json-in",
          default="tiles_basic.json",
          help="Input .json without addresses")
      parser.add_argument(
          "--json-out", default="tilegrid.json", help="Output JSON")
      args = parser.parse_args()
  
      run(args.json_in, args.json_out, verbose=args.verbose)
  
  
  if __name__ == "__main__":
      main()
  