| #!/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 re |
| import argparse |
| import json |
| import functools |
| |
| NUMBER_RE = re.compile(r'\d+$') |
| |
| |
| def check_sequential(speed_model): |
| # combinational path models do not contain |
| # the following keywords |
| timing_keywords = { |
| 'setup': 'setup', |
| 'remov': 'removal', |
| 'hold': 'hold', |
| 'recov': 'recovery', |
| 'removal': 'removal', |
| 'recovery': 'recovery' |
| } |
| tmp = speed_model.split('_') |
| for keyword in sorted(timing_keywords): |
| if keyword in tmp: |
| # return found keyword and it's map in SDF |
| return [keyword, timing_keywords[keyword]] |
| |
| return None |
| |
| |
| # FF's can be configured to work as FF or latch |
| def check_ff_latch(speed_model): |
| |
| tmp = speed_model.split('_') |
| if 'ff' in tmp: |
| return 'ff' |
| elif 'lat' in tmp: |
| return 'lat' |
| else: |
| return None |
| |
| |
| # some bels have duplicate names e.g bufmrce_bufmrce |
| # this function cleans them |
| def clean_bname(bname): |
| tmp = bname.split('_') |
| if len(tmp) > 1 and tmp[0] == tmp[1]: |
| return '_'.join(tmp[1:]) |
| return bname |
| |
| |
| def find_aliased_pin(pin, model, pin_aliases): |
| """ |
| Searches for aliased pins in the timing model. |
| The check is done using data from pin_aliases dictionary. |
| The dictionary has an entry for each aliased pin. |
| Each entry has two fields: |
| |
| * names : a list of all the possible aliases |
| * is_property_related: a flag saying if the alias is in fact |
| pin name combined with BEL property (e.g. Q[LH] pins |
| in FF - in this case the pin name is Q [original name], |
| but is named Q[LH] in the timing model. The suffix |
| determines polarity of the FF's set/reset input). |
| |
| If is_property_related is set the function returns the original |
| pin name, aliased name is returned otherwise. |
| |
| Parameters |
| ---------- |
| pin: str |
| Pin name to look for |
| model: str |
| Timing model |
| pin_aliases: dict |
| A dict of list of aliases for given bel/site |
| |
| Returns |
| ------- |
| bool, str |
| |
| The first bool value is set to true if pin is found |
| in the timing model, false otherwise. |
| |
| The second returned value is found pin name. If pin |
| is not found None is returned |
| |
| >>> find_aliased_pin("a", "a_b_some_test_string", None) |
| (False, None) |
| |
| >>> find_aliased_pin("d", "din_dout_setup", {"D": {"names" : ["din"], "is_property_related" : False}}) |
| (True, 'din') |
| |
| >>> find_aliased_pin("d", "din_dout_setup", {"D": {"names" : ["din"], "is_property_related" : True}}) |
| (True, 'd') |
| |
| >>> find_aliased_pin("d", "din_dout_setup", {"D": {"names" : ["notdin"], "is_property_related" : True}}) |
| (False, None) |
| """ |
| if (pin_aliases is not None) and (pin.upper() in pin_aliases): |
| for alias in pin_aliases[pin.upper()]['names']: |
| single_word_alias = (len(alias.split('_')) == 1) |
| pin_alias = alias.lower() |
| if single_word_alias: |
| model_to_check = model.split('_') |
| else: |
| model_to_check = model |
| if pin_alias in model_to_check: |
| if pin_aliases[pin.upper()]['is_property_related']: |
| return True, pin.lower() |
| else: |
| return True, pin_alias |
| |
| return False, None |
| |
| |
| def instance_in_model(instance, model): |
| |
| if len(instance.split('_')) == 1: |
| # instance name is one word, search it in the model |
| return instance in model.split('_') |
| else: |
| # instance name is multi word, search for a string |
| return instance in model |
| |
| |
| def create_pin_in_model(pin_aliases): |
| """ |
| Checks if a given pin belongs to the model. |
| |
| Parameters |
| ---------- |
| pin: str |
| Pin name to look for |
| pin_aliases: dict |
| A dict of list of aliases for given bel/site |
| model: str |
| Timing model name |
| direction: str |
| Optional pin direction suffix [IN|OUT] |
| |
| Returns |
| ------- |
| bool, str |
| |
| The first returned value is set to true if pin is found, |
| false otherwise. |
| |
| The second returned value contains found pin name. If the |
| pin is not found, None is returned. |
| |
| >>> create_pin_in_model(None)("d", "ff_init_din_q", "in") |
| (True, 'din') |
| |
| >>> create_pin_in_model(None)("q", "ff_init_clk_q", None) |
| (True, 'q') |
| |
| >>> create_pin_in_model({"Q": {"names" : ["QL", "QH"], "is_property_related" : True}})("q", "ff_init_clk_ql", None) |
| (True, 'q') |
| |
| >>> create_pin_in_model(None)("logic_out", "my_cell_i_logic_out", None) |
| (True, 'logic_out') |
| |
| >>> create_pin_in_model({"LOGIC_OUT": {"names" : ["LOGIC_O", "O"], "is_property_related" : False}})("logic_out", "my_cell_i_logic_o", None) |
| (True, 'logic_o') |
| |
| >>> create_pin_in_model({"LOGIC_OUT": {"names" : ["LOGIC_O", "O"], "is_property_related" : False}})("logic_out", "my_cell_i_o", None) |
| (True, 'o') |
| """ |
| |
| @functools.lru_cache(maxsize=10000) |
| def pin_in_model(pin, model, direction=None): |
| # strip site location |
| model = model.split(':')[0] |
| |
| extended_pin_name = pin |
| aliased_pin, aliased_pin_name = find_aliased_pin( |
| pin.upper(), model, pin_aliases) |
| |
| # some timings reports pins with their directions |
| # this happens for e.g. CLB reg_init D pin, which |
| # timing is reported as DIN |
| if direction is not None: |
| extended_pin_name = pin + direction |
| |
| if instance_in_model(pin, model): |
| return True, pin |
| elif instance_in_model(extended_pin_name, model): |
| return True, extended_pin_name |
| elif aliased_pin: |
| return True, aliased_pin_name |
| else: |
| return False, None |
| |
| return pin_in_model |
| |
| |
| def remove_pin_from_model(pin, model): |
| """ |
| Removes the pin from model name if present. |
| |
| Arguments |
| --------- |
| |
| pin: str |
| Pin name |
| mode: str |
| Timing model name |
| |
| Returns |
| ------- |
| |
| str |
| Updated timing model name |
| |
| >>> remove_pin_from_model("q", "ff_init_d_q") |
| 'ff_init_d' |
| >>> remove_pin_from_model("q", "ff_init_d_ql") |
| 'ff_init_d_ql' |
| >>> remove_pin_from_model("logic_out", "ff_init_d_logic_out") |
| 'ff_init_d' |
| >>> remove_pin_from_model("logic_out", "ff_init_d_second_out") |
| 'ff_init_d_second_out' |
| """ |
| |
| if len(pin.split('_')) == 1: |
| # pin name is one word, search it in the model |
| tmp = model.split('_') |
| if pin in tmp: |
| tmp.remove(pin) |
| return "_".join(tmp) |
| else: |
| return model |
| else: |
| # pin name is multi word, search for a string |
| return "_".join(list(filter(None, model.replace(pin, '').split('_')))) |
| |
| |
| def merged_dict(itr): |
| """ Create a merged dict of dict (of dict) based on input. |
| |
| Input is an iteratable of (keys, value). |
| |
| Return value is root dictionary |
| |
| Keys are successive dictionaries indicies. For example: |
| (('a', 'b', 'c'), 1) |
| |
| would set: |
| |
| output['a']['b']['c'] = 1 |
| |
| This function returns an error if two values conflict. |
| |
| >>> merged_dict(((('a', 'b', 'c'), 1), (('a', 'b', 'd'), 2))) |
| {'a': {'b': {'c': 1, 'd': 2}}} |
| |
| """ |
| |
| output = {} |
| for keys, value in itr: |
| target = output |
| for key in keys[:-1]: |
| if key not in target: |
| target[key] = {} |
| target = target[key] |
| |
| if keys[-1] in target: |
| assert target[keys[-1]] == value, (keys, value, target[keys[-1]]) |
| else: |
| target[keys[-1]] = value |
| |
| return output |
| |
| |
| def extract_properties(tile, site, bel, properties, model): |
| |
| if tile not in properties: |
| return None |
| if site not in properties[tile]: |
| return None |
| if bel not in properties[tile][site]: |
| return None |
| |
| model_properties = dict() |
| |
| for prop in properties[tile][site][bel]: |
| if prop in model_properties: |
| continue |
| if instance_in_model(prop.lower(), model): |
| # if there is property there must be value |
| # value always follow the property |
| for value in properties[tile][site][bel][prop]: |
| value = value.replace(',', '') |
| prop_val_str = "_".join([prop, value]) |
| if instance_in_model(prop_val_str.lower(), model): |
| model_properties[prop] = value |
| break |
| |
| return model_properties |
| |
| |
| def parse_raw_timing(fin): |
| with open(fin, "r") as f: |
| for line in f: |
| raw_data = line.split() |
| slice = raw_data[0] |
| |
| sites_count = int(raw_data[1]) |
| loc = 2 |
| for site in range(0, sites_count): |
| |
| site_name = raw_data[loc] |
| bels_count = int(raw_data[loc + 1]) |
| |
| # read all BELs data within |
| loc += 2 |
| for bel in range(0, bels_count): |
| bel = raw_data[loc] |
| delay_count = int(raw_data[loc + 1]) |
| |
| # get all the delays |
| loc += 2 |
| for delay in range(0, delay_count): |
| speed_model = raw_data[loc] |
| |
| # each timing entry reports 5 delays |
| timing = [ |
| raw_data[d + 1 + loc].split(':') |
| for d in range(0, 5) |
| ] |
| |
| yield slice, site_name, bel, speed_model, timing |
| |
| # 5 delay values + name |
| loc += 6 |
| |
| |
| def read_raw_timings(fin, properties, pins, site_pins, pin_alias_map): |
| def inner(): |
| raw = list(parse_raw_timing(fin)) |
| |
| pin_in_models = {} |
| |
| for slice, site_name, bel, speed_model, timing in raw: |
| btype = bel.lower() |
| delay_btype = clean_bname(btype) |
| delay_btype_orig = delay_btype |
| |
| # all the bel names seem to start with "bel_d_" |
| # let's get rid of it |
| if speed_model.startswith('bel_d_'): |
| speed_model = speed_model[6:] |
| |
| # keep original speed model string to use as unique dict entry |
| speed_model_orig = speed_model |
| |
| # if more than one BEL type exists in the slice |
| # location is added at the end of the name |
| tmp = speed_model.split(':') |
| speed_model = tmp[0] |
| |
| bel_location = site_name |
| if len(tmp) > 2: |
| bel_location += "/" + "/".join(tmp[2:]) |
| |
| bel_location = bel_location.upper() |
| |
| sequential = check_sequential(speed_model) |
| if sequential is not None: |
| tmp = speed_model.split('_') |
| tmp.remove(sequential[0]) |
| speed_model = '_'.join(tmp) |
| |
| bel_input = None |
| bel_output = None |
| bel_clock = None |
| |
| # strip btype from speed model so we can search for pins |
| speed_model_clean = speed_model |
| if speed_model.startswith(delay_btype): |
| speed_model_clean = speed_model[len(delay_btype):] |
| |
| # remove properties from the model |
| speed_model_properties = extract_properties( |
| slice, site_name, delay_btype_orig, properties, |
| speed_model_clean) |
| if speed_model_properties is not None: |
| for prop in speed_model_properties: |
| # properties values in the model always follow properties name |
| prop_string = "_".join( |
| [prop, speed_model_properties[prop]]) |
| speed_model_clean = remove_pin_from_model( |
| prop_string.lower(), speed_model_clean) |
| |
| # Get pin alias map |
| if delay_btype not in pin_in_models: |
| pin_aliases = pin_alias_map.get(delay_btype, None) |
| pin_in_models[delay_btype] = create_pin_in_model(pin_aliases) |
| |
| pin_in_model = pin_in_models[delay_btype] |
| |
| # locate pins |
| for pin in pins[slice][site_name][delay_btype_orig]: |
| orig_pin = pin |
| pim, pin = pin_in_model(pin.lower(), speed_model_clean, 'in') |
| |
| if pim: |
| if pins[slice][site_name][delay_btype_orig][orig_pin][ |
| 'is_clock'] and not pins[slice][site_name][ |
| delay_btype_orig][orig_pin]['is_part_of_bus']: |
| bel_clock = pin |
| bel_clock_orig_pin = orig_pin |
| elif pins[slice][site_name][delay_btype_orig][orig_pin][ |
| 'direction'] == 'IN': |
| bel_input = pin |
| elif pins[slice][site_name][delay_btype_orig][orig_pin][ |
| 'direction'] == 'OUT': |
| bel_output = pin |
| speed_model_clean = remove_pin_from_model( |
| pin.lower(), speed_model_clean) |
| |
| # Some speed models describe delays from/to site pins instead of BEL pins |
| if bel_clock is None: |
| for pin in site_pins[slice][site_name.lower()]: |
| orig_pin = pin |
| pim, pin = pin_in_model(pin.lower(), speed_model_clean) |
| if pim: |
| if site_pins[slice][site_name.lower( |
| )][orig_pin]['is_clock'] and not site_pins[slice][ |
| site_name.lower()][orig_pin]['is_part_of_bus']: |
| bel_clock = pin |
| bel_clock_orig_pin = orig_pin |
| speed_model_clean = remove_pin_from_model( |
| pin.lower(), speed_model_clean) |
| |
| if bel_input is None: |
| # search site inputs |
| for pin in site_pins[slice][site_name.lower()]: |
| orig_pin = pin |
| pim, pin = pin_in_model( |
| pin.lower(), speed_model_clean, 'in') |
| if pim: |
| if site_pins[slice][site_name.lower( |
| )][orig_pin]['direction'] == 'IN': |
| bel_input = pin |
| speed_model_clean = remove_pin_from_model( |
| pin.lower(), speed_model_clean) |
| |
| if bel_output is None: |
| for pin in site_pins[slice][site_name.lower()]: |
| orig_pin = pin |
| pim, pin = pin_in_model(pin.lower(), speed_model_clean) |
| if pim: |
| if site_pins[slice][site_name.lower( |
| )][orig_pin]['direction'] == 'OUT': |
| bel_output = pin |
| speed_model_clean = remove_pin_from_model( |
| pin.lower(), speed_model_clean) |
| |
| # if we couldn't find input, check if the clock is the |
| # only input. This applies only to combinational paths |
| if (sequential is None) and (bel_input is None) and (bel_clock is |
| not None): |
| if bel_clock_orig_pin in site_pins[slice][site_name.lower()] and \ |
| site_pins[slice][site_name.lower( |
| )][bel_clock_orig_pin]['direction'] == 'IN': |
| bel_input = bel_clock |
| |
| # if we still don't have the input check if the input |
| # is wider than 1 bit and timing defined for the whole |
| # port |
| if (bel_input is None) or (bel_output is None): |
| for pin in pins[slice][site_name][delay_btype_orig]: |
| number = NUMBER_RE.search(pin) |
| if number is not None: |
| orig_pin = pin[:-(len(str(number.group())))] |
| orig_pin_full = pin |
| pim, pin = pin_in_model( |
| orig_pin.lower(), speed_model_clean) |
| if not pim: |
| # some inputs pins are named with unsignificant zeros |
| # remove ti and try again |
| orig_pin = orig_pin + str(int(number.group())) |
| pim, pin = pin_in_model( |
| orig_pin.lower(), speed_model_clean) |
| |
| if pim: |
| if pins[slice][site_name][delay_btype_orig][orig_pin_full]['direction'] == 'IN' \ |
| and bel_input is None: |
| bel_input = pin |
| if pins[slice][site_name][delay_btype_orig][orig_pin_full]['direction'] == 'OUT' \ |
| and bel_output is None: |
| bel_output = pin |
| speed_model_clean = remove_pin_from_model( |
| orig_pin.lower(), speed_model_clean) |
| |
| # check if the input is not a BEL property |
| if bel_input is None: |
| # if there is anything not yet decoded |
| if len(speed_model_clean.split("_")) > 1: |
| if len(speed_model_properties.keys()) == 1: |
| bel_input = list(speed_model_properties.keys())[0] |
| |
| # if we still don't have input, give up |
| if bel_input is None: |
| continue |
| |
| # restore speed model name |
| speed_model = delay_btype + speed_model_clean |
| |
| if sequential is not None: |
| if bel_clock is None: |
| continue |
| |
| if bel_output is None and bel_clock is None or \ |
| bel_output is None and bel_clock == bel_input: |
| continue |
| else: |
| if bel_input is None or bel_output is None: |
| continue |
| |
| delay_btype = speed_model |
| # add properties to the delay_btype |
| if speed_model_properties is not None: |
| for prop in sorted(speed_model_properties): |
| prop_string = "_".join( |
| [prop, speed_model_properties[prop]]) |
| delay_btype += "_" + prop_string |
| |
| yield (slice, bel_location, delay_btype, speed_model_orig, |
| 'type'), btype.upper() |
| yield ( |
| slice, bel_location, delay_btype, speed_model_orig, |
| 'input'), bel_input.upper() |
| |
| if bel_output is not None: |
| yield ( |
| slice, bel_location, delay_btype, speed_model_orig, |
| 'output'), bel_output.upper() |
| |
| if bel_clock is not None: |
| yield ( |
| slice, bel_location, delay_btype, speed_model_orig, |
| 'clock'), bel_clock.upper() |
| |
| yield ( |
| slice, bel_location, delay_btype, speed_model_orig, |
| 'location'), bel_location.upper() |
| |
| #XXX: debug |
| yield ( |
| slice, bel_location, delay_btype, speed_model_orig, |
| 'model'), speed_model_orig |
| |
| if sequential is not None: |
| assert bel_clock is not None, ( |
| slice, bel_location, delay_btype, speed_model_orig) |
| yield ( |
| slice, bel_location, delay_btype, speed_model_orig, |
| 'sequential'), sequential[1] |
| |
| for t, v in timing: |
| yield ( |
| slice, bel_location, delay_btype, speed_model_orig, t), v |
| |
| return merged_dict(inner()) |
| |
| |
| def read_bel_properties(properties_file, properties_map): |
| def inner(): |
| with open(properties_file, 'r') as f: |
| for line in f: |
| raw_props = line.split() |
| tile = raw_props[0] |
| sites_count = int(raw_props[1]) |
| prop_loc = 2 |
| |
| if sites_count == 0: |
| yield (tile, ), {} |
| |
| for site in range(0, sites_count): |
| site_name = raw_props[prop_loc] |
| bels_count = int(raw_props[prop_loc + 1]) |
| prop_loc += 2 |
| |
| for bel in range(0, bels_count): |
| bel_name = raw_props[prop_loc] |
| bel_name = clean_bname(bel_name) |
| bel_name = bel_name.lower() |
| bel_properties_count = int(raw_props[prop_loc + 1]) |
| |
| props = 0 |
| prop_loc += 2 |
| for prop in range(0, bel_properties_count): |
| prop_name = raw_props[prop_loc] |
| |
| # the name always starts with "CONFIG." and ends with ".VALUES" |
| # let's get rid of that |
| if prop_name.startswith( |
| 'CONFIG.') and prop_name.endswith( |
| '.VALUES'): |
| prop_name = prop_name[7:-7] |
| |
| prop_values_count = int(raw_props[prop_loc + 1]) |
| |
| if prop_name not in [ |
| 'RAM_MODE', |
| 'WRITE_WIDTH_A', |
| 'WRITE_WIDTH_B', |
| 'READ_WIDTH_A', |
| 'READ_WIDTH_B', |
| ]: |
| if bel_name in properties_map: |
| if prop_name in properties_map[bel_name]: |
| prop_name = properties_map[bel_name][ |
| prop_name] |
| |
| yield (tile, site_name, bel_name, prop_name), \ |
| raw_props[prop_loc + 2:prop_loc + 2 + |
| prop_values_count] |
| props += 1 |
| |
| prop_loc += 2 + prop_values_count |
| |
| if props == 0: |
| yield (tile, site_name, bel_name), {} |
| |
| return merged_dict(inner()) |
| |
| |
| def read_bel_pins(pins_file): |
| def inner(): |
| with open(pins_file, 'r') as f: |
| for line in f: |
| raw_pins = line.split() |
| tile = raw_pins[0] |
| sites_count = int(raw_pins[1]) |
| pin_loc = 2 |
| |
| if sites_count == 0: |
| yield (tile, ), {} |
| |
| for site in range(0, sites_count): |
| site_name = raw_pins[pin_loc] |
| bels_count = int(raw_pins[pin_loc + 1]) |
| pin_loc += 2 |
| |
| for bel in range(0, bels_count): |
| bel_name = raw_pins[pin_loc] |
| bel_name = clean_bname(bel_name) |
| bel_name = bel_name.lower() |
| bel_pins_count = int(raw_pins[pin_loc + 1]) |
| |
| pin_loc += 2 |
| for pin in range(0, bel_pins_count): |
| pin_name = raw_pins[pin_loc] |
| pin_direction = raw_pins[pin_loc + 1] |
| pin_is_clock = raw_pins[pin_loc + 2] |
| pin_is_part_of_bus = raw_pins[pin_loc + 3] |
| |
| yield ( |
| tile, site_name, bel_name, pin_name, |
| 'direction'), pin_direction |
| yield ( |
| tile, site_name, bel_name, pin_name, |
| 'is_clock'), int(pin_is_clock) == 1 |
| yield ( |
| tile, site_name, bel_name, pin_name, |
| 'is_part_of_bus' |
| ), int(pin_is_part_of_bus) == 1 |
| pin_loc += 4 |
| |
| return merged_dict(inner()) |
| |
| |
| def read_site_pins(pins_file): |
| def inner(): |
| with open(pins_file, 'r') as f: |
| for line in f: |
| raw_pins = line.split() |
| tile = raw_pins[0] |
| site_count = int(raw_pins[1]) |
| pin_loc = 2 |
| |
| if site_count == 0: |
| yield (tile, ), {} |
| |
| for site in range(0, site_count): |
| site_name = raw_pins[pin_loc] |
| site_name = site_name.lower() |
| site_pins_count = int(raw_pins[pin_loc + 1]) |
| |
| pin_loc += 2 |
| for pin in range(0, site_pins_count): |
| pin_name = raw_pins[pin_loc] |
| pin_direction = raw_pins[pin_loc + 1] |
| pin_is_part_of_bus = raw_pins[pin_loc + 2] |
| |
| yield ( |
| (tile, site_name, pin_name, 'direction'), |
| pin_direction) |
| yield ( |
| (tile, site_name, pin_name, 'is_clock'), |
| pin_name.lower() == 'clk') |
| yield ( |
| (tile, site_name, pin_name, 'is_part_of_bus'), |
| int(pin_is_part_of_bus)) |
| |
| # site clock pins are always named 'CLK' |
| pin_loc += 3 |
| |
| return merged_dict(inner()) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--timings', type=str, help='Raw timing input file') |
| parser.add_argument('--json', type=str, help='json output file') |
| parser.add_argument( |
| '--properties', type=str, help='Bel properties input file') |
| parser.add_argument('--belpins', type=str, help='Bel pins input file') |
| parser.add_argument('--sitepins', type=str, help='Site pins input file') |
| parser.add_argument( |
| '--debug', type=bool, default=False, help='Enable debug json dumps') |
| parser.add_argument( |
| '--propertiesmap', type=str, help='Properties names mappings') |
| parser.add_argument( |
| '--pinaliasmap', type=str, help='Pin name alias mappings') |
| args = parser.parse_args() |
| |
| with open(args.propertiesmap, 'r') as fp: |
| properties_map = json.load(fp) |
| |
| with open(args.pinaliasmap, 'r') as fp: |
| pin_alias_map = json.load(fp) |
| |
| properties = read_bel_properties(args.properties, properties_map) |
| |
| if args.debug: |
| with open('debug_prop.json', 'w') as fp: |
| json.dump(properties, fp, indent=4, sort_keys=True) |
| |
| pins = read_bel_pins(args.belpins) |
| if args.debug: |
| with open('debug_pins.json', 'w') as fp: |
| json.dump(pins, fp, indent=4, sort_keys=True) |
| |
| site_pins = read_site_pins(args.sitepins) |
| if args.debug: |
| with open('debug_site_pins.json', 'w') as fp: |
| json.dump(site_pins, fp, indent=4, sort_keys=True) |
| |
| timings = read_raw_timings( |
| args.timings, properties, pins, site_pins, pin_alias_map) |
| with open(args.json, 'w') as fp: |
| json.dump(timings, fp, indent=4, sort_keys=True) |
| |
| |
| if __name__ == '__main__': |
| main() |