Merge pull request #857 from antmicro/fuzzer_007_ff_sr_to_q

Fixed fuzzer 007 to make it extract missing timings for FFs
This commit is contained in:
Karol Gugala 2019-06-19 08:15:45 +02:00 committed by GitHub
commit 0f37f0c294
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 412 additions and 25 deletions

View File

@ -6,8 +6,16 @@ clean:
build/bel_timings.txt:
bash runme.sh
build/bel_timings.json: build/bel_timings.txt
python3 tim2json.py --timings=build/bel_timings.txt --json=build/bel_timings.json --properties=build/bel_properties.txt --propertiesmap=properties_map.json --belpins=build/bel_pins.txt --sitepins=build/tile_pins.txt --debug true
build/fixup_timings: build/bel_timings.txt
python3 fixup_timings_txt.py --txtin build/bel_timings.txt --txtout build/bel_timings.txt --site RAMBFIFO36E1 --slice BRAM_L --type timings
python3 fixup_timings_txt.py --txtin build/bel_timings.txt --txtout build/bel_timings.txt --site RAMBFIFO36E1 --slice BRAM_R --type timings
python3 fixup_timings_txt.py --txtin build/bel_pins.txt --txtout build/bel_pins.txt --site RAMBFIFO36E1 --slice BRAM_L --type pins
python3 fixup_timings_txt.py --txtin build/bel_pins.txt --txtout build/bel_pins.txt --site RAMBFIFO36E1 --slice BRAM_R --type pins
python3 fixup_timings_txt.py --txtin build/bel_properties.txt --txtout build/bel_properties.txt --site RAMBFIFO36E1 --slice BRAM_L --type properties
python3 fixup_timings_txt.py --txtin build/bel_properties.txt --txtout build/bel_properties.txt --site RAMBFIFO36E1 --slice BRAM_R --type properties
build/bel_timings.json: build/fixup_timings
python3 tim2json.py --timings=build/bel_timings.txt --json=build/bel_timings.json --properties=build/bel_properties.txt --propertiesmap=properties_map.json --pinaliasmap=pin_alias_map.json --belpins=build/bel_pins.txt --sitepins=build/tile_pins.txt --debug true
build/sdf: build/bel_timings.json
python3 ${XRAY_UTILS_DIR}/makesdf.py --json=${PWD}/build/bel_timings.json --sdf=${PWD}/build

View File

@ -0,0 +1,45 @@
# BEL timings fuzzer
This fuzzer is suppose to extract all the BEL timings for Xilinx 7-series FPGAs.
The extracted timings are saved as SDF files.
Single SDF file is produced for every FPGA tile type.
## Fuzzer flow
Fuzzer flow is the following:
Vivado/`runme.tcl` -> `fixup_timings_txt.py` -> `tim2json.py` -> `makesdf.py` -> `sdfmerge.py`
The fuzzer uses Vivado tcl script to read all the FPGA timings data.
This data is dumped to a text file.
Beside timings data the tcl scipt dumps the following information:
* Sites pins
* BELs pins
* BELs attributes
The tcl script iterates over every BEL for all the sites in each tile type.
The timings, pins and attributes are dumped in the same hierarchy.
This hierarchy is used by the `tim2json` script to process the txt dump.
Main task of the `tim2json` script is to figure out what the dumped timings mean and what paths do they describe.
This is done by parsing the string timings, searching the BEL/Sites pins and BEL attributes.
Since for some pins there was no naming convention, the names are provided directly via `pin_alias_map.json`.
Some BELs (e.g. BRAMs) reports the timings with incorrect site name.
To work around this issue the `fixup_timings_txt.py` script is used to flatten the timings dump file.
As a result all the timings for a selected tile are squashed into one site.
### Text files format
The files dumped by the Vivado scrips have the following format:
Each line starts with a tile name, proceeded by a number of sites in the tile.
After the tiles count, each tile info is placed.
The tile info starts with a tile name followed by number of BELs in the tile.
The bels number is followed by BEL entries.
Each BEL entry starts with BEL name followed by a number of entries.
The entries differ between the file types:
* timings file: each timing entry starts with entry name followed by 5 timings values
* properties file: each properties entry starts with property name followed by a number of possible property values, followed by a list of the possible property values
* pins file: each pin entry starts with pin name, followed by pin direction (IN/OUT), followed by 0 or 1 flag saying if the pin is a clock pin

View File

@ -0,0 +1,104 @@
#!/usr/bin/env python3
import argparse
def fix_line(line, site, filetype):
"""
Squashes the entries for multiple sites into one.
This is required when entries are defined for a different
site than they are reported.
Such situation happend e.g.
for BRAM_[LR]. All the entries are defined for RAMBFIFO36E1,
while they are reported for RAMB18E1 or FIFO18E1
Parameters
----------
line: str
raw dump file line
site: str
site to which all the entries will be squashed
filetype: str
entries type. One of [timings, pins, properties]
Returns
-------
str
line with squashed entries
"""
assert filetype in [
"timings", "pins", "properties"
], "Unsupported filetype"
line = line.split()
newline = list()
sites_count = int(line[1])
newline.append(line[0])
# we'll emit only one site
newline.append("1")
newline.append(site)
newline.append("1")
newline.append(site)
entries = list()
all_entries = 0
loc = 2
for site in range(0, sites_count):
bels_count = int(line[loc + 1])
loc += 2
for bel in range(0, bels_count):
entries_count = int(line[loc + 1])
loc += 2
all_entries += entries_count
for entry in range(0, entries_count):
if filetype == 'timings':
for delay_word in range(0, 6):
entries.append(line[loc])
loc += 1
elif filetype == 'pins':
for pin_word in range(0, 3):
entries.append(line[loc])
loc += 1
elif filetype == 'properties':
entries.append(line[loc])
loc += 1
values_count = int(line[loc])
entries.append(line[loc])
loc += 1
for value in range(0, values_count):
entries.append(line[loc])
loc += 1
newline.append(str(all_entries))
newline.extend(entries)
return " ".join(newline) + "\n"
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--txtin', type=str, help='Input text file')
parser.add_argument('--txtout', type=str, help='Output text file')
parser.add_argument(
'--site',
type=str,
help='Site to which the entries should be squashed')
parser.add_argument(
'--slice', type=str, help='Slice for which the entries shall be fixed')
parser.add_argument(
'--type', type=str, choices=['timings', 'pins', 'properties'])
args = parser.parse_args()
lines = list()
with open(args.txtin, 'r') as fp:
for line in fp:
if line.startswith(args.slice):
line = fix_line(line, args.site, args.type)
lines.append(line)
with open(args.txtout, 'w') as fp:
for line in lines:
fp.write(line)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,60 @@
{
"ff_init": {
"Q": {
"names" : ["QL", "QH"],
"is_property_related" : true
}
},
"reg_init": {
"Q": {
"names" : ["QL", "QH"],
"is_property_related" : true
}
},
"rambfifo36e1" : {
"RSTRAMARSTRAMLRST" : {
"names" : ["rstramarstl"],
"is_property_related" : false
},
"ADDRARDADDRU0" : {
"names" : ["addrau"],
"is_property_related" : false
},
"ADDRARDADDRL15" : {
"names" : ["addra15l"],
"is_property_related" : false
},
"RSTRAMARSTRAMU" : {
"names" : ["rstramau"],
"is_property_related" : false
},
"ADDRBWRADDRU0" : {
"names" : ["addrbu"],
"is_property_related" : false
},
"ADDRBWRADDRL15" : {
"names" : ["addrb15l"],
"is_property_related" : false
},
"WEBWEU0" : {
"names" : ["webu"],
"is_property_related" : false
},
"DOADO0" : {
"names" : ["doadol", "doadou"],
"is_property_related" : false
},
"DOBDO0" : {
"names" : ["dobdol", "dobdou"],
"is_property_related" : false
},
"DOPADOP0" : {
"names" : ["dopadopl", "dopadopu"],
"is_property_related" : false
},
"DOPBDOP0" : {
"names" : ["dopbdopl", "dopbdopu"],
"is_property_related" : false
}
}
}

View File

@ -45,11 +45,120 @@ def clean_bname(bname):
return bname
def pin_in_model(pin, model, direction=None):
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 pin_in_model(pin, pin_aliases, model, direction=None):
"""
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.
>>> pin_in_model("d", None, "ff_init_din_q", "in")
(True, 'din')
>>> pin_in_model("q", None, "ff_init_clk_q", None)
(True, 'q')
>>> pin_in_model("q", {"Q": {"names" : ["QL", "QH"], "is_property_related" : True}}, "ff_init_clk_ql", None)
(True, 'q')
>>> pin_in_model("logic_out", None, "my_cell_i_logic_out", None)
(True, 'logic_out')
>>> pin_in_model("logic_out", {"LOGIC_OUT": {"names" : ["LOGIC_O", "O"], "is_property_related" : False}}, "my_cell_i_logic_o", None)
(True, 'logic_o')
>>> pin_in_model("logic_out", {"LOGIC_OUT": {"names" : ["LOGIC_O", "O"], "is_property_related" : False}}, "my_cell_i_o", None)
(True, 'o')
"""
# 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
@ -63,26 +172,62 @@ def pin_in_model(pin, model, direction=None):
return True, pin
elif extended_pin_name in model.split('_'):
return True, extended_pin_name
elif aliased_pin:
return True, aliased_pin_name
else:
return False, None
else:
# pin name is multi word, search for a string
return (pin in model), pin
if pin in model:
return True, pin
elif aliased_pin:
return True, aliased_pin_name
else:
return False, None
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('_')
tmp.remove(pin)
return "_".join(tmp)
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 read_raw_timings(fin, properties, pins, site_pins):
def read_raw_timings(fin, properties, pins, site_pins, pin_alias_map):
timings = dict()
with open(fin, "r") as f:
@ -150,15 +295,21 @@ def read_raw_timings(fin, properties, pins, site_pins):
if speed_model.startswith(delay_btype):
speed_model_clean = speed_model[len(delay_btype):]
# Get pin alias map
pin_aliases = pin_alias_map.get(delay_btype, None)
# 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')
pin.lower(), pin_aliases, speed_model_clean,
'in')
if pim:
if pins[slice][site_name][delay_btype_orig][
orig_pin]['is_clock']:
bel_clock = pin
bel_clock_orig_pin = orig_pin
elif pins[slice][site_name][delay_btype_orig][
orig_pin]['direction'] == 'IN':
bel_input = pin
@ -168,11 +319,13 @@ def read_raw_timings(fin, properties, pins, site_pins):
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)
pin.lower(), pin_aliases,
speed_model_clean)
if pim:
if site_pins[slice][site_name.lower(
)][orig_pin]['is_clock']:
@ -181,13 +334,13 @@ def read_raw_timings(fin, properties, pins, site_pins):
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_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')
pin.lower(), pin_aliases,
speed_model_clean, 'in')
if pim:
if site_pins[slice][site_name.lower(
)][orig_pin]['direction'] == 'IN':
@ -199,7 +352,8 @@ def read_raw_timings(fin, properties, pins, site_pins):
for pin in site_pins[slice][site_name.lower()]:
orig_pin = pin
pim, pin = pin_in_model(
pin.lower(), speed_model_clean)
pin.lower(), pin_aliases,
speed_model_clean)
if pim:
if site_pins[slice][site_name.lower(
)][orig_pin]['direction'] == 'OUT':
@ -207,6 +361,15 @@ def read_raw_timings(fin, properties, pins, site_pins):
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
# check if the input is not a BEL property
if bel_input is None:
# if there is anything not yet decoded
@ -218,38 +381,38 @@ def read_raw_timings(fin, properties, pins, site_pins):
speed_model_clean = remove_pin_from_model(
prop.lower(), speed_model_clean)
break
# if we couldn't find input, check if the clock is the
# only input
if 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
import re
if bel_input is None:
if (bel_input is None) or (bel_output is None):
for pin in pins[slice][site_name][
delay_btype_orig]:
number = re.search(r'\d+$', 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)
orig_pin.lower(), pin_aliases,
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(),
orig_pin.lower(), pin_aliases,
speed_model_clean)
if pim:
bel_input = pin
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)
@ -263,7 +426,8 @@ def read_raw_timings(fin, properties, pins, site_pins):
speed_model = delay_btype + speed_model_clean
if sequential is not None:
if bel_output is None and bel_clock is None:
if bel_output is None and bel_clock is None or \
bel_output is None and bel_clock == bel_input:
delay_loc += 6
continue
else:
@ -439,11 +603,16 @@ def main():
'--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:
@ -460,7 +629,8 @@ def main():
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)
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)