blob: bbde9263ce29a6e7f7308d69fff69a12b543d467 [file] [log] [blame]
import re
from collections import OrderedDict
try:
#Try for the fast c-based version first
import xml.etree.cElementTree as ET
except ImportError:
#Fall back on python implementation
import xml.etree.ElementTree as ET
from util import load_config_lines
from error import InspectError
class ParsePattern:
def __init__(self, name, filename, regex_str, default_value=None):
self._name = name
self._filename = filename
self._regex = re.compile(regex_str)
self._default_value = default_value
def name(self):
return self._name
def filename(self):
return self._filename
def regex(self):
return self._regex
def default_value(self):
return self._default_value
class PassRequirement(object):
def __init__(self, metric):
self._metric = metric
self._type = type
def metric(self):
return self._metric
def type(self):
raise NotImplementedError()
def check_passed(golden_value, check_value):
raise NotImplementedError()
class EqualPassRequirement(PassRequirement):
def __init__(self, metric):
super(EqualPassRequirement, self).__init__(metric)
def type(self):
return "Equal"
def check_passed(self, golden_value, check_value):
if golden_value == check_value:
return True, ""
else:
return False, "Task value '{}' does not match golden value '{}'".format(golden_value, check_value)
class RangePassRequirement(PassRequirement):
def __init__(self, metric, min_value=None, max_value=None):
super(RangePassRequirement, self).__init__(metric)
if max_value < min_value:
raise InspectError("Invalid range specification (max value larger than min value)")
self._min_value = min_value
self._max_value = max_value
def type(self):
return "Range"
def min_value(self):
return self._min_value
def max_value(self):
return self._max_value
def check_passed(self, golden_value, check_value):
if golden_value == None and check_value == None:
return True, "both golden and check are None"
elif golden_value == None and check_value != None:
return False, "golden value is None, but check value is {}".format(check_value)
elif golden_value != None and check_value == None:
return False, "golden value is {}, but check value is None".format(golden_value)
assert golden_value != None
assert check_value != None
try:
golden_value = float(golden_value)
except ValueError as e:
raise InspectError("Failed to convert golden value '{}' to float".format(golden_value))
try:
check_value = float(check_value)
except ValueError as e:
raise InspectError("Failed to convert check value '{}' to float".format(check_value))
if golden_value == 0.: #Avoid division by zero
if golden_value == check_value:
return True, "golden and check both equal 0"
else:
return False, "unable to normalize relative value (golden value is zero)".format(norm_check_value, min_value(), max_value())
else:
norm_check_value = check_value / golden_value
if self.min_value() <= norm_check_value <= self.max_value():
return True, "relative value within range"
else:
return False, "relative value {} outside of range [{},{}]".format(norm_check_value, min_value(), max_value())
class ParseResults:
def __init__(self):
self._metrics = OrderedDict()
def primary_keys(self):
return ("architecture", "circuit")
def add_result(self, arch, circuit, parse_result):
self._metrics[(arch, circuit)] = parse_result
def metrics(self, arch, circuit):
if (arch, circuit) in self._metrics:
return self._metrics[(arch, circuit)]
else:
return None
def all_metrics(self):
return self._metrics
def load_parse_patterns(parse_config_filepath):
parse_patterns = OrderedDict()
for line in load_config_lines(parse_config_filepath):
components = line.split(';')
if len(components) == 3 or len(components) == 4:
name = components[0]
filepath = components[1]
regex_str = components[2]
default_value = None
if len(components) == 4:
default_value = components[3]
if name not in parse_patterns:
parse_patterns[name] = ParsePattern(name, filepath, regex_str, default_value)
else:
raise InspectError("Duplicate parse pattern name '{}'".format(name), parse_config_filepath)
else:
raise InspectError("Invalid parse format line: '{}'".format(line), parse_config_filepath)
return parse_patterns
def load_pass_requirements(pass_requirements_filepath):
parse_patterns = OrderedDict()
for line in load_config_lines(pass_requirements_filepath):
components = line.split(';')
if len(components) == 2:
metric = components[0]
expr = components[1]
if metric not in parse_patterns:
func, params_str = expr.split("(")
params_str = params_str.rstrip(")")
if params_str == "":
params = []
else:
params = params_str.split(",")
if func == "Range":
if len(params) != 2:
raise InspectError("Range() pass requirement function requires two arguments", pass_requirements_filepath)
parse_patterns[metric] = RangePassRequirement(metric, float(params[0]), float(params[1]))
elif func == "Equal":
if len(params) != 0:
raise InspectError("Equal() pass requirement function requires no arguments", pass_requirements_filepath)
parse_patterns[metric] = EqualPassRequirement(metric)
else:
raise InspectError("Unexpected pass requirement function '{}' for metric '{}'".format(func, metric), pass_requirements_filepath)
else:
raise InspectError("Duplicate pass requirement for '{}'".format(metric), pass_requirements_filepath)
else:
raise InspectError("Invalid pass requirement format line: '{}'".format(line), pass_requirements_filepath)
return parse_patterns
def load_parse_results(parse_results_filepath, primary_key_set=None):
header = []
parse_results = ParseResults()
with open(parse_results_filepath) as f:
for lineno, row in enumerate(f):
elements = [elem.strip() for elem in row.split("\t")]
if lineno == 0:
header = elements
else:
primary_keys = OrderedDict()
result = OrderedDict()
arch=None
circuit=None
for i, elem in enumerate(elements):
metric = header[i]
if elem == "":
elem = None
if metric == "arch":
metric = "architecture"
if metric == "architecture":
arch = elem
elif metric == "circuit":
circuit = elem
result[metric] = elem
assert arch and circuit
parse_results.add_result(arch, circuit, result)
return parse_results
def determine_lut_size(architecture_file):
"""
Determines the maximum LUT size (K) in an architecture file.
Assumes LUTs are represented as BLIF '.names'
"""
arch_xml = ET.parse(architecture_file).getroot()
lut_size = 0
saw_blif_names = False
for elem in arch_xml.findall('.//pb_type'): #Xpath recrusive search for 'pb_type'
blif_model = elem.get('blif_model')
if blif_model and blif_model == ".names":
saw_blif_names = True
input_port = elem.find('input')
input_width = int(input_port.get('num_pins'))
assert input_width > 0
#Keep the maximum lut size found (i.e. fracturable architectures)
lut_size = max(lut_size, input_width)
if saw_blif_names and lut_size == 0:
raise InspectError(msg="Could not identify valid LUT size (K)",
filename=architecture_file)
return lut_size
def determine_memory_addr_width(architecture_file):
"""
Determines the maximum RAM block address width in an architecture file
Assumes RAMS are represented using the standard VTR primitives (.subckt single_port_ram, .subckt dual_port_ram etc.)
"""
arch_xml = ET.parse(architecture_file).getroot()
mem_addr_width = 0
saw_ram = False
for elem in arch_xml.findall('.//pb_type'): #XPATH for recursive search
blif_model = elem.get('blif_model')
if blif_model and "port_ram" in blif_model:
saw_ram = True
for input_port in elem.findall('input'):
port_name = input_port.get('name')
if "addr" in port_name:
input_width = int(input_port.get('num_pins'))
mem_addr_width = max(mem_addr_width, input_width)
if saw_ram and mem_addr_width == 0:
raise InspectError(msg="Could not identify RAM block address width",
filename=architecture_file)
return mem_addr_width
def determine_min_W(log_filename):
min_W_regex = re.compile(r"\s*Best routing used a channel width factor of (?P<min_W>\d+).")
with open(log_filename) as f:
for line in f:
match = min_W_regex.match(line)
if match:
return int(match.group("min_W"))
raise InspectError(msg="Failed to find minimum channel width.",
filename=log_filename)