From a320b63164ec12dd123e0f62c46b51e78b72d31c Mon Sep 17 00:00:00 2001 From: John Wright Date: Wed, 27 Feb 2019 23:03:05 -0800 Subject: [PATCH 01/10] initial metrics commit, WIP --- src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 src/hammer-vlsi/hammer_vlsi/hammer_metrics.py diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py new file mode 100644 index 000000000..557788712 --- /dev/null +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# hammer_metrics.py +# +# Design metrics traits and utilities for hammer-vlsi +# +# See LICENSE for licence details. + +from hammer_util import add_dicts +from typing import NamedTuple, Optional, List, Any, Dict, Callable +import yaml + +class ModuleSpec(NamedTuple('ModuleSpec', [ + ('path', List[str]) +])): + __slots__ = () + +class PathSpec(NamedTuple('PathSpec', [ + ('path', List[str]) +])): + __slots__ = () + +class TimingPathSpec(NamedTuple('TimingPathSpec', [ + ('to', Optional[PathSpec]), + ('from', Optional[PathSpec]), + ('through', Optional[PathSpec]) +])): + __slots__ = () + +class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ + ('module', ModuleSpec), + ('clock', Optional[PathSpec]), + ('target', Optional[float]), + ('value', Optional[float]) +])): + __slots__ = () + + @staticmethod + def from_ir(ir: Dict[str, Union[str, List[str]]]) -> CriticalPathEntry: + pass + +class TimingPathEntry(NamedTuple('TimingPathEntry', [ + ('timing_path', TimingPathSpec), + ('clock', Optional[PathSpec]), + ('target', Optional[float]), + ('value', Optional[float]) +])): + __slots__ = () + + @staticmethod + def from_ir(ir: Dict[str, Union[str, List[str]]]) -> TimingPathEntry: + pass + +class ModuleAreaEntry(NamedTuple('ModuleAreaEntry', [ + ('module', TimingPathSpec), + ('value', Optional[float]) +])): + __slots__ = () + + @staticmethod + def from_ir(ir: Dict[str, Union[str, List[str]]]) -> ModuleAreaEntry: + pass + +MetricsDBEntry = Union[CriticalPathEntry, TimingPathEntry, ModuleAreaEntry] +SupportMap = Dict[str, Callable[MetricsDBEntry, List[str]] + +FromIRMap = { + "critical path": CriticalPathEntry.from_ir, + "timing path": TimingPathEntry.from_ir, + "area": ModuleAreaEntry.from_ir +} + +class MetricsDB: + + def __init__(self): + self._db = {} # type: Dict[str, MetricsDBEntry] + + def create_entry(self, key: str, entry: MetricsDBEntry) -> None: + if key in self._db: + raise ValueError("Duplicate entry in MetricsDB: {}".format(key)) + else: + self._db[key] = entry + + def get_entry(self, key: str) -> MetricsDBEntry: + if key in self._db: + return self._db[key] + else: + raise ValueError("Entry not found in MetricsDB: {}".format(key)) + + @property + def entries(self) -> Dict[str, MetricsDBEntry]: + return self._db + +class HasMetricSupport(HammerTool): + + @property + def _support_map(self) -> SupportMap: + return {} # type: SupportMap + + def _is_supported(self, entry: MetricsDBEntry) -> bool: + return (entry.__class__ in _support_map) + + def create_metrics_db_from_ir(self, ir: Union[str, file]) -> MetricsDB: + # convert to a dict + y = yaml.load(ir) + # create a db + db = MetricsDB() + if self.namespace in y: + testcases = y[self.namespace] + for testcase in testcases: + key = "{}.{}".format(self.namespace, self.testcase) + testcase_data = testcases[testcase] + mtype = testcase_data["type"] # type: Dict[str, Union[str, List[str]]] + if mtype in FromIRMap: + entry = FromIRMap[mtype](testcase_data) # type: MetricsDBEntry + db.create_entry(key, entry) + else: + raise ValueError("Metric IR field <{}> is not supported. Did you forget to update FromIRMap?") + return db + + def generate_metric_requests_from_db(self, db: MetricsDB) -> List[str]: + output = [] + for key in db.entries: + entry = db.get_entry(key) + if self._is_supported(entry): + output.extend(self._support_map[entry.__class__.__name__](entry)) + return output + + def generate_metric_requests_from_ir(self, ir: Union[str, file]) -> List[str]: + return self.generate_metric_requests_from_db(self.create_metrics_db_from_ir(ir)) + + # This will be the key phrase used in the IR + @property + @abstractmethod + def namespace(self) -> str: + pass + +class HasAreaMetricSupport(HasMetricSupport): + + @property + def _support_map(self) -> SupportMap: + return reduce(add_dicts, [super()._support_map, { + 'ModuleAreaEntry': self.get_module_area + }]) + + @abstractmethod + def get_module_area(self, key: str, module: ModuleSpec) -> List[str]: + pass + +class HasTimingPathMetricSupport(HasMetricSupport): + + @property + def _support_map(self) -> SupportMap: + return reduce(add_dicts, [super()._support_map, { + 'CriticalPathEntry': self.get_critical_path, + 'TimingPathEntry': self.get_timing_path + }]) + + @abstractmethod + def get_critical_path(self, key: str, module: ModuleSpec) -> List[str]: + pass + + @abstractmethod + def get_timing_path(self, key: str, timing_path: TimingPathSpec) -> List[str]: + pass From 30fbe1b7cab1f6a26e83796aa03455982305bb53 Mon Sep 17 00:00:00 2001 From: John Wright Date: Tue, 5 Mar 2019 11:27:25 -0800 Subject: [PATCH 02/10] WIP --- src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py index 557788712..60d45e962 100644 --- a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -16,21 +16,21 @@ class ModuleSpec(NamedTuple('ModuleSpec', [ ])): __slots__ = () -class PathSpec(NamedTuple('PathSpec', [ +class PortSpec(NamedTuple('PortSpec', [ ('path', List[str]) ])): __slots__ = () class TimingPathSpec(NamedTuple('TimingPathSpec', [ - ('to', Optional[PathSpec]), - ('from', Optional[PathSpec]), - ('through', Optional[PathSpec]) + ('to', Optional[PortSpec]), + ('from', Optional[PortSpec]), + ('through', Optional[PortSpec]) ])): __slots__ = () class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ ('module', ModuleSpec), - ('clock', Optional[PathSpec]), + ('clock', Optional[PortSpec]), # TODO make this connect to HammerIR clock entry somehow (HammerClockSpec??) ('target', Optional[float]), ('value', Optional[float]) ])): @@ -38,11 +38,12 @@ class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ @staticmethod def from_ir(ir: Dict[str, Union[str, List[str]]]) -> CriticalPathEntry: + # Not yet implemented pass class TimingPathEntry(NamedTuple('TimingPathEntry', [ ('timing_path', TimingPathSpec), - ('clock', Optional[PathSpec]), + ('clock', Optional[PortSpec]), # TODO same as above ('target', Optional[float]), ('value', Optional[float]) ])): @@ -50,20 +51,23 @@ class TimingPathEntry(NamedTuple('TimingPathEntry', [ @staticmethod def from_ir(ir: Dict[str, Union[str, List[str]]]) -> TimingPathEntry: + # Not yet implemented pass class ModuleAreaEntry(NamedTuple('ModuleAreaEntry', [ - ('module', TimingPathSpec), + ('module', ModuleSpec), ('value', Optional[float]) ])): __slots__ = () @staticmethod def from_ir(ir: Dict[str, Union[str, List[str]]]) -> ModuleAreaEntry: + # Not yet implemented pass +# TODO document this MetricsDBEntry = Union[CriticalPathEntry, TimingPathEntry, ModuleAreaEntry] -SupportMap = Dict[str, Callable[MetricsDBEntry, List[str]] +SupportMap = Dict[str, Callable[Tuple[str, MetricsDBEntry], List[str]] FromIRMap = { "critical path": CriticalPathEntry.from_ir, @@ -116,7 +120,7 @@ def create_metrics_db_from_ir(self, ir: Union[str, file]) -> MetricsDB: entry = FromIRMap[mtype](testcase_data) # type: MetricsDBEntry db.create_entry(key, entry) else: - raise ValueError("Metric IR field <{}> is not supported. Did you forget to update FromIRMap?") + raise ValueError("Metric IR field <{}> is not supported. Did you forget to update FromIRMap?".format(mtype)) return db def generate_metric_requests_from_db(self, db: MetricsDB) -> List[str]: @@ -124,7 +128,7 @@ def generate_metric_requests_from_db(self, db: MetricsDB) -> List[str]: for key in db.entries: entry = db.get_entry(key) if self._is_supported(entry): - output.extend(self._support_map[entry.__class__.__name__](entry)) + output.extend(self._support_map[entry.__class__.__name__](key, entry)) return output def generate_metric_requests_from_ir(self, ir: Union[str, file]) -> List[str]: From 892819c4de067a7d133b7d8356aca2480f8a49b2 Mon Sep 17 00:00:00 2001 From: John Wright Date: Wed, 6 Mar 2019 23:22:29 -0800 Subject: [PATCH 03/10] Make mypy happy --- src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py index 60d45e962..72a3008c9 100644 --- a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -7,8 +7,11 @@ # # See LICENSE for licence details. -from hammer_util import add_dicts -from typing import NamedTuple, Optional, List, Any, Dict, Callable +from hammer_utils import add_dicts +from hammer_vlsi import HammerTool +from abc import abstractmethod +from typing import NamedTuple, Optional, List, Any, Dict, Callable, Union, TextIO +from functools import reduce import yaml class ModuleSpec(NamedTuple('ModuleSpec', [ @@ -21,13 +24,16 @@ class PortSpec(NamedTuple('PortSpec', [ ])): __slots__ = () +# I would like to call these "to" and "from" but "from" is a keyword in python class TimingPathSpec(NamedTuple('TimingPathSpec', [ - ('to', Optional[PortSpec]), - ('from', Optional[PortSpec]), + ('start', Optional[PortSpec]), + ('end', Optional[PortSpec]), ('through', Optional[PortSpec]) ])): __slots__ = () + # TODO assert that one of the 3 values is not None + class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ ('module', ModuleSpec), ('clock', Optional[PortSpec]), # TODO make this connect to HammerIR clock entry somehow (HammerClockSpec??) @@ -67,13 +73,14 @@ def from_ir(ir: Dict[str, Union[str, List[str]]]) -> ModuleAreaEntry: # TODO document this MetricsDBEntry = Union[CriticalPathEntry, TimingPathEntry, ModuleAreaEntry] -SupportMap = Dict[str, Callable[Tuple[str, MetricsDBEntry], List[str]] +#SupportMap = Dict[str, Callable[[str, MetricsDBEntry], List[str]]] +SupportMap = Dict[str, Callable[[str, Any], List[str]]] FromIRMap = { "critical path": CriticalPathEntry.from_ir, "timing path": TimingPathEntry.from_ir, "area": ModuleAreaEntry.from_ir -} +} # type: Dict[str, Callable[[Dict[str, Union[str, List[str]]]], MetricsDBEntry]] class MetricsDB: @@ -100,12 +107,12 @@ class HasMetricSupport(HammerTool): @property def _support_map(self) -> SupportMap: - return {} # type: SupportMap + return {} def _is_supported(self, entry: MetricsDBEntry) -> bool: - return (entry.__class__ in _support_map) + return (entry.__class__ in self._support_map) - def create_metrics_db_from_ir(self, ir: Union[str, file]) -> MetricsDB: + def create_metrics_db_from_ir(self, ir: Union[str, TextIO]) -> MetricsDB: # convert to a dict y = yaml.load(ir) # create a db @@ -113,9 +120,9 @@ def create_metrics_db_from_ir(self, ir: Union[str, file]) -> MetricsDB: if self.namespace in y: testcases = y[self.namespace] for testcase in testcases: - key = "{}.{}".format(self.namespace, self.testcase) + key = "{}.{}".format(self.namespace, testcase) testcase_data = testcases[testcase] - mtype = testcase_data["type"] # type: Dict[str, Union[str, List[str]]] + mtype = testcase_data["type"] # type: str if mtype in FromIRMap: entry = FromIRMap[mtype](testcase_data) # type: MetricsDBEntry db.create_entry(key, entry) @@ -124,14 +131,14 @@ def create_metrics_db_from_ir(self, ir: Union[str, file]) -> MetricsDB: return db def generate_metric_requests_from_db(self, db: MetricsDB) -> List[str]: - output = [] + output = [] # type: List[str] for key in db.entries: entry = db.get_entry(key) if self._is_supported(entry): output.extend(self._support_map[entry.__class__.__name__](key, entry)) return output - def generate_metric_requests_from_ir(self, ir: Union[str, file]) -> List[str]: + def generate_metric_requests_from_ir(self, ir: Union[str, TextIO]) -> List[str]: return self.generate_metric_requests_from_db(self.create_metrics_db_from_ir(ir)) # This will be the key phrase used in the IR @@ -144,27 +151,29 @@ class HasAreaMetricSupport(HasMetricSupport): @property def _support_map(self) -> SupportMap: - return reduce(add_dicts, [super()._support_map, { + x = reduce(add_dicts, [super()._support_map, { 'ModuleAreaEntry': self.get_module_area - }]) + }]) # type: SupportMap + return x @abstractmethod - def get_module_area(self, key: str, module: ModuleSpec) -> List[str]: + def get_module_area(self, key: str, entry: ModuleAreaEntry) -> List[str]: pass class HasTimingPathMetricSupport(HasMetricSupport): @property def _support_map(self) -> SupportMap: - return reduce(add_dicts, [super()._support_map, { + x = reduce(add_dicts, [super()._support_map, { 'CriticalPathEntry': self.get_critical_path, 'TimingPathEntry': self.get_timing_path - }]) + }]) # type: SupportMap + return x @abstractmethod - def get_critical_path(self, key: str, module: ModuleSpec) -> List[str]: + def get_critical_path(self, key: str, entry: CriticalPathEntry) -> List[str]: pass @abstractmethod - def get_timing_path(self, key: str, timing_path: TimingPathSpec) -> List[str]: + def get_timing_path(self, key: str, entry: TimingPathEntry) -> List[str]: pass From f6a71b2bdc6e72a0aa7d2c6f4e2c885a4412d3f1 Mon Sep 17 00:00:00 2001 From: John Wright Date: Wed, 6 Mar 2019 23:26:03 -0800 Subject: [PATCH 04/10] Use a type alias for IRType to clean things up a bit --- src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py index 72a3008c9..81c75212a 100644 --- a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -24,6 +24,9 @@ class PortSpec(NamedTuple('PortSpec', [ ])): __slots__ = () +# TODO document me +IRType = Dict[str, Union[str, List[str]]] + # I would like to call these "to" and "from" but "from" is a keyword in python class TimingPathSpec(NamedTuple('TimingPathSpec', [ ('start', Optional[PortSpec]), @@ -43,7 +46,7 @@ class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ __slots__ = () @staticmethod - def from_ir(ir: Dict[str, Union[str, List[str]]]) -> CriticalPathEntry: + def from_ir(ir: IRType) -> CriticalPathEntry: # Not yet implemented pass @@ -56,7 +59,7 @@ class TimingPathEntry(NamedTuple('TimingPathEntry', [ __slots__ = () @staticmethod - def from_ir(ir: Dict[str, Union[str, List[str]]]) -> TimingPathEntry: + def from_ir(ir: IRType) -> TimingPathEntry: # Not yet implemented pass @@ -67,7 +70,7 @@ class ModuleAreaEntry(NamedTuple('ModuleAreaEntry', [ __slots__ = () @staticmethod - def from_ir(ir: Dict[str, Union[str, List[str]]]) -> ModuleAreaEntry: + def from_ir(ir: IRType) -> ModuleAreaEntry: # Not yet implemented pass @@ -80,7 +83,7 @@ def from_ir(ir: Dict[str, Union[str, List[str]]]) -> ModuleAreaEntry: "critical path": CriticalPathEntry.from_ir, "timing path": TimingPathEntry.from_ir, "area": ModuleAreaEntry.from_ir -} # type: Dict[str, Callable[[Dict[str, Union[str, List[str]]]], MetricsDBEntry]] +} # type: Dict[str, Callable[[IRType], MetricsDBEntry]] class MetricsDB: From d4fc9858159597caa7104a2a95d78a7af48a600b Mon Sep 17 00:00:00 2001 From: John Wright Date: Wed, 6 Mar 2019 23:47:05 -0800 Subject: [PATCH 05/10] Implement IR parsing --- src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py index 81c75212a..bf3b2eb1d 100644 --- a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -19,11 +19,19 @@ class ModuleSpec(NamedTuple('ModuleSpec', [ ])): __slots__ = () + @staticmethod + def from_str(s: str) -> ModuleSpec: + return ModuleSpec(s.split("/")) + class PortSpec(NamedTuple('PortSpec', [ ('path', List[str]) ])): __slots__ = () + @staticmethod + def from_str(s: str) -> PortSpec: + return PortSpec(s.split("/")) + # TODO document me IRType = Dict[str, Union[str, List[str]]] @@ -35,7 +43,19 @@ class TimingPathSpec(NamedTuple('TimingPathSpec', [ ])): __slots__ = () - # TODO assert that one of the 3 values is not None + @staticmethod + def from_ir(ir: IRType) -> TimingPathSpec: + start = ir["start"] if "start" in ir else "" + end = ir["end"] if "end" in ir else "" + through = ir["through"] if "through" in ir else "" + assert isinstance(start, str) + assert isinstance(end, str) + assert isinstance(through, str) + startspec = PortSpec.from_str(start) if "start" in ir else None + endspec = PortSpec.from_str(end) if "end" in ir else None + throughspec = PortSpec.from_str(through) if "through" in ir else None + assert startspec is not None or endspec is not None or throughspec is not None, "At least one of start, end, or through must not be None" + return TimingPathSpec(startspec, endspec, throughspec) class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ ('module', ModuleSpec), @@ -47,8 +67,18 @@ class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ @staticmethod def from_ir(ir: IRType) -> CriticalPathEntry: - # Not yet implemented - pass + try: + module = ir["module"] + clock = ir["clock"] if "clock" in ir else "" + assert isinstance(module, str) + assert isinstance(clock, str) + return CriticalPathEntry( + ModuleSpec.from_str(module), + PortSpec.from_str(clock) if "clock" in ir else None, + None, + None) + except: + raise ValueError("Invalid IR for CriticalPathEntry: {}".format(ir)) class TimingPathEntry(NamedTuple('TimingPathEntry', [ ('timing_path', TimingPathSpec), @@ -60,8 +90,16 @@ class TimingPathEntry(NamedTuple('TimingPathEntry', [ @staticmethod def from_ir(ir: IRType) -> TimingPathEntry: - # Not yet implemented - pass + try: + clock = ir["clock"] if "clock" in ir else "" + assert isinstance(clock, str) + return TimingPathEntry( + TimingPathSpec.from_ir(ir), + PortSpec.from_str(clock) if "clock" in ir else None, + None, + None) + except: + raise ValueError("Invalid IR for TimingPathEntry: {}".format(ir)) class ModuleAreaEntry(NamedTuple('ModuleAreaEntry', [ ('module', ModuleSpec), @@ -71,8 +109,14 @@ class ModuleAreaEntry(NamedTuple('ModuleAreaEntry', [ @staticmethod def from_ir(ir: IRType) -> ModuleAreaEntry: - # Not yet implemented - pass + try: + mod = ir["module"] + assert isinstance(mod, str) + return ModuleAreaEntry( + ModuleSpec.from_str(mod), + None) + except: + raise ValueError("Invalid IR for TimingPathEntry: {}".format(ir)) # TODO document this MetricsDBEntry = Union[CriticalPathEntry, TimingPathEntry, ModuleAreaEntry] From 8e23cf1c5620d789e10f9a7ef36ee1fcab2fae9b Mon Sep 17 00:00:00 2001 From: John Wright Date: Sat, 16 Mar 2019 17:18:39 -0700 Subject: [PATCH 06/10] This works now --- src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 50 +++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py index bf3b2eb1d..11f926268 100644 --- a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -13,15 +13,24 @@ from typing import NamedTuple, Optional, List, Any, Dict, Callable, Union, TextIO from functools import reduce import yaml +import copy +import os +# Note: Do not include the top module in the module spec +# e.g. [] = root +# ['inst1'] = inst at first level of hierarchy class ModuleSpec(NamedTuple('ModuleSpec', [ ('path', List[str]) ])): __slots__ = () @staticmethod - def from_str(s: str) -> ModuleSpec: - return ModuleSpec(s.split("/")) + def from_str(s: str) -> 'ModuleSpec': + return ModuleSpec(list(filter(lambda x: x != '', s.split("/")))) + + @property + def is_top(self) -> bool: + return len(self.path) == 0 class PortSpec(NamedTuple('PortSpec', [ ('path', List[str]) @@ -29,8 +38,8 @@ class PortSpec(NamedTuple('PortSpec', [ __slots__ = () @staticmethod - def from_str(s: str) -> PortSpec: - return PortSpec(s.split("/")) + def from_str(s: str) -> 'PortSpec': + return PortSpec(list(filter(lambda x: x != '', s.split("/")))) # TODO document me IRType = Dict[str, Union[str, List[str]]] @@ -44,7 +53,7 @@ class TimingPathSpec(NamedTuple('TimingPathSpec', [ __slots__ = () @staticmethod - def from_ir(ir: IRType) -> TimingPathSpec: + def from_ir(ir: IRType) -> 'TimingPathSpec': start = ir["start"] if "start" in ir else "" end = ir["end"] if "end" in ir else "" through = ir["through"] if "through" in ir else "" @@ -66,7 +75,7 @@ class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ __slots__ = () @staticmethod - def from_ir(ir: IRType) -> CriticalPathEntry: + def from_ir(ir: IRType) -> 'CriticalPathEntry': try: module = ir["module"] clock = ir["clock"] if "clock" in ir else "" @@ -89,7 +98,7 @@ class TimingPathEntry(NamedTuple('TimingPathEntry', [ __slots__ = () @staticmethod - def from_ir(ir: IRType) -> TimingPathEntry: + def from_ir(ir: IRType) -> 'TimingPathEntry': try: clock = ir["clock"] if "clock" in ir else "" assert isinstance(clock, str) @@ -108,7 +117,7 @@ class ModuleAreaEntry(NamedTuple('ModuleAreaEntry', [ __slots__ = () @staticmethod - def from_ir(ir: IRType) -> ModuleAreaEntry: + def from_ir(ir: IRType) -> 'ModuleAreaEntry': try: mod = ir["module"] assert isinstance(mod, str) @@ -157,11 +166,14 @@ def _support_map(self) -> SupportMap: return {} def _is_supported(self, entry: MetricsDBEntry) -> bool: - return (entry.__class__ in self._support_map) + return (entry.__class__.__name__ in self._support_map) def create_metrics_db_from_ir(self, ir: Union[str, TextIO]) -> MetricsDB: # convert to a dict - y = yaml.load(ir) + y = yaml.load(ir) # type: Optional[Dict[Str, Any]] + if y is None: + y = {} + assert(isinstance(y, dict)) # create a db db = MetricsDB() if self.namespace in y: @@ -169,6 +181,8 @@ def create_metrics_db_from_ir(self, ir: Union[str, TextIO]) -> MetricsDB: for testcase in testcases: key = "{}.{}".format(self.namespace, testcase) testcase_data = testcases[testcase] + if "type" not in testcase_data: + raise ValueError("Missing \"type\" field in testcase {}".format(testcase)) mtype = testcase_data["type"] # type: str if mtype in FromIRMap: entry = FromIRMap[mtype](testcase_data) # type: MetricsDBEntry @@ -188,6 +202,12 @@ def generate_metric_requests_from_db(self, db: MetricsDB) -> List[str]: def generate_metric_requests_from_ir(self, ir: Union[str, TextIO]) -> List[str]: return self.generate_metric_requests_from_db(self.create_metrics_db_from_ir(ir)) + def generate_metric_requests_from_file(self, filename: str) -> List[str]: + if not os.path.isfile(filename): + raise ValueError("Metrics IR file {} does not exist or is not a file".format(filename)) + with open(filename, "r") as f: + return self.generate_metric_requests_from_ir(f) + # This will be the key phrase used in the IR @property @abstractmethod @@ -198,9 +218,10 @@ class HasAreaMetricSupport(HasMetricSupport): @property def _support_map(self) -> SupportMap: - x = reduce(add_dicts, [super()._support_map, { + x = copy.deepcopy(super()._support_map) # type: SupportMap + x.update({ 'ModuleAreaEntry': self.get_module_area - }]) # type: SupportMap + }) return x @abstractmethod @@ -211,10 +232,11 @@ class HasTimingPathMetricSupport(HasMetricSupport): @property def _support_map(self) -> SupportMap: - x = reduce(add_dicts, [super()._support_map, { + x = copy.deepcopy(super()._support_map) # type: SupportMap + x.update({ 'CriticalPathEntry': self.get_critical_path, 'TimingPathEntry': self.get_timing_path - }]) # type: SupportMap + }) return x @abstractmethod From 52ca2dbf4320d0499b95970fb03f3069a4964d22 Mon Sep 17 00:00:00 2001 From: John Wright Date: Mon, 18 Mar 2019 22:22:33 -0700 Subject: [PATCH 07/10] please the robot --- src/hammer-vlsi/defaults.yml | 4 ++++ src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hammer-vlsi/defaults.yml b/src/hammer-vlsi/defaults.yml index 80ae20e0a..be4748225 100644 --- a/src/hammer-vlsi/defaults.yml +++ b/src/hammer-vlsi/defaults.yml @@ -395,6 +395,10 @@ drc.inputs: # Custom DRC command text to add after the boilerplate commands at the top of the run file additional_drc_text: "" +vlsi.metrics: + # path to the metrics IR input (o1ptional string) + input_path: null + # inherit settings from vlsi.submit but allow us to override them drc.submit: command: "${vlsi.submit.command}" diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py index 11f926268..f8628a59e 100644 --- a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -170,7 +170,7 @@ def _is_supported(self, entry: MetricsDBEntry) -> bool: def create_metrics_db_from_ir(self, ir: Union[str, TextIO]) -> MetricsDB: # convert to a dict - y = yaml.load(ir) # type: Optional[Dict[Str, Any]] + y = yaml.load(ir) # type: Optional[Dict[str, Any]] if y is None: y = {} assert(isinstance(y, dict)) From 19e74c597686a72963c8e647d547c83db1137811 Mon Sep 17 00:00:00 2001 From: John Wright Date: Wed, 20 Mar 2019 00:34:57 -0700 Subject: [PATCH 08/10] Add a tree structure for storing module paths --- src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py index f8628a59e..04eadec6b 100644 --- a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -28,6 +28,9 @@ class ModuleSpec(NamedTuple('ModuleSpec', [ def from_str(s: str) -> 'ModuleSpec': return ModuleSpec(list(filter(lambda x: x != '', s.split("/")))) + def append(self, child: str) -> 'ModuleSpec': + return ModuleSpec(self.path + [child]) + @property def is_top(self) -> bool: return len(self.path) == 0 @@ -138,10 +141,59 @@ def from_ir(ir: IRType) -> 'ModuleAreaEntry': "area": ModuleAreaEntry.from_ir } # type: Dict[str, Callable[[IRType], MetricsDBEntry]] +class ModuleTree: + + index = 0 + + def __init__(self): + self._children = {} # type: Dict[str, ModuleTree] + self._rename_id = index + ModuleTree.index += 1 + self._no_ungroup = False + # More properties go here + + def get_or_create_node(self, name: str) -> 'ModuleTree': + if name in self._children: + return self._children[name] + else: + node = ModuleTree() + self._children[name] = node + return node + + def get_no_ungroup_paths(self, prefix: Optional[ModuleSpec] = None) -> List[ModuleSpec]: + result = [] # type: List[ModuleSpec] + for name, child in self._children.items(): + new_prefix = ModuleSpec([name]) + if prefix is not None: + new_prefix = prefix.append(name) + if child.get_no_ungroup: + result.append(new_prefix) + result.extend(child.get_no_ungroup_paths(new_prefix)) + return result + + def add_module(self, m: ModuleSpec) -> 'ModuleTree': + child = self.get_or_create_node(m.path[0]) + if len(m.path) > 1: + return child.add_module(ModuleSpec(m.path[1:])) + else: + return child + + @property + def get_no_ungroup(self) -> bool: + return self._no_ungroup + + def set_no_ungroup(self, val: bool = True) -> None: + self._no_ungroup = val + + @property + def is_leaf(self) -> bool: + return len(self._children) == 0 + class MetricsDB: def __init__(self): self._db = {} # type: Dict[str, MetricsDBEntry] + self._tree = ModuleTree() def create_entry(self, key: str, entry: MetricsDBEntry) -> None: if key in self._db: @@ -159,6 +211,10 @@ def get_entry(self, key: str) -> MetricsDBEntry: def entries(self) -> Dict[str, MetricsDBEntry]: return self._db + @property + def module_tree(self) -> ModuleTree: + return self._tree + class HasMetricSupport(HammerTool): @property From 219a17e42869be18b611d9d00bbe84a16c095649 Mon Sep 17 00:00:00 2001 From: John Wright Date: Thu, 28 Mar 2019 13:23:38 -0700 Subject: [PATCH 09/10] Remove TimingPathEntry since it doesn't map well to innovus, don't use deepcopy because _support_map is flat and it was causing recursion issues, and change how PortSpec works --- src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 129 ++++++++---------- 1 file changed, 57 insertions(+), 72 deletions(-) diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py index 04eadec6b..b9a6c5dc2 100644 --- a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -35,46 +35,43 @@ def append(self, child: str) -> 'ModuleSpec': def is_top(self) -> bool: return len(self.path) == 0 + @property + def to_str(self) -> str: + return "/".join(self.path) + class PortSpec(NamedTuple('PortSpec', [ - ('path', List[str]) + ('module', ModuleSpec), + ('port', str) ])): __slots__ = () @staticmethod def from_str(s: str) -> 'PortSpec': - return PortSpec(list(filter(lambda x: x != '', s.split("/")))) + tmp = s.split(':') + if len(tmp) != 2: + raise ValueError("Invalid port spec: " + s) + mod = ModuleSpec.from_str(tmp[0]) + return PortSpec(mod, tmp[1]) + + @property + def to_str(self) -> str: + return self.module.to_str + ":" + self.port # TODO document me IRType = Dict[str, Union[str, List[str]]] -# I would like to call these "to" and "from" but "from" is a keyword in python -class TimingPathSpec(NamedTuple('TimingPathSpec', [ - ('start', Optional[PortSpec]), - ('end', Optional[PortSpec]), - ('through', Optional[PortSpec]) -])): - __slots__ = () +class MetricsDBEntry: - @staticmethod - def from_ir(ir: IRType) -> 'TimingPathSpec': - start = ir["start"] if "start" in ir else "" - end = ir["end"] if "end" in ir else "" - through = ir["through"] if "through" in ir else "" - assert isinstance(start, str) - assert isinstance(end, str) - assert isinstance(through, str) - startspec = PortSpec.from_str(start) if "start" in ir else None - endspec = PortSpec.from_str(end) if "end" in ir else None - throughspec = PortSpec.from_str(through) if "through" in ir else None - assert startspec is not None or endspec is not None or throughspec is not None, "At least one of start, end, or through must not be None" - return TimingPathSpec(startspec, endspec, throughspec) + @abstractmethod + def register(self, db: 'MetricsDB') -> None: + pass class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ ('module', ModuleSpec), ('clock', Optional[PortSpec]), # TODO make this connect to HammerIR clock entry somehow (HammerClockSpec??) ('target', Optional[float]), ('value', Optional[float]) -])): +]), MetricsDBEntry): __slots__ = () @staticmethod @@ -92,31 +89,13 @@ def from_ir(ir: IRType) -> 'CriticalPathEntry': except: raise ValueError("Invalid IR for CriticalPathEntry: {}".format(ir)) -class TimingPathEntry(NamedTuple('TimingPathEntry', [ - ('timing_path', TimingPathSpec), - ('clock', Optional[PortSpec]), # TODO same as above - ('target', Optional[float]), - ('value', Optional[float]) -])): - __slots__ = () - - @staticmethod - def from_ir(ir: IRType) -> 'TimingPathEntry': - try: - clock = ir["clock"] if "clock" in ir else "" - assert isinstance(clock, str) - return TimingPathEntry( - TimingPathSpec.from_ir(ir), - PortSpec.from_str(clock) if "clock" in ir else None, - None, - None) - except: - raise ValueError("Invalid IR for TimingPathEntry: {}".format(ir)) + def register(self, db: 'MetricsDB') -> None: + db.module_tree.add_module(self.module) class ModuleAreaEntry(NamedTuple('ModuleAreaEntry', [ ('module', ModuleSpec), ('value', Optional[float]) -])): +]), MetricsDBEntry): __slots__ = () @staticmethod @@ -128,16 +107,18 @@ def from_ir(ir: IRType) -> 'ModuleAreaEntry': ModuleSpec.from_str(mod), None) except: - raise ValueError("Invalid IR for TimingPathEntry: {}".format(ir)) + raise ValueError("Invalid IR for ModuleAreaEntry: {}".format(ir)) + + def register(self, db: 'MetricsDB') -> None: + db.module_tree.add_module(self.module) # TODO document this -MetricsDBEntry = Union[CriticalPathEntry, TimingPathEntry, ModuleAreaEntry] +#MetricsDBEntry = Union[CriticalPathEntry, ModuleAreaEntry] #SupportMap = Dict[str, Callable[[str, MetricsDBEntry], List[str]]] SupportMap = Dict[str, Callable[[str, Any], List[str]]] FromIRMap = { "critical path": CriticalPathEntry.from_ir, - "timing path": TimingPathEntry.from_ir, "area": ModuleAreaEntry.from_ir } # type: Dict[str, Callable[[IRType], MetricsDBEntry]] @@ -147,7 +128,7 @@ class ModuleTree: def __init__(self): self._children = {} # type: Dict[str, ModuleTree] - self._rename_id = index + self._rename_id = ModuleTree.index ModuleTree.index += 1 self._no_ungroup = False # More properties go here @@ -192,24 +173,32 @@ def is_leaf(self) -> bool: class MetricsDB: def __init__(self): - self._db = {} # type: Dict[str, MetricsDBEntry] + self._db = {} # type: Dict[str, Dict[str, MetricsDBEntry]] self._tree = ModuleTree() - def create_entry(self, key: str, entry: MetricsDBEntry) -> None: - if key in self._db: + def create_entry(self, namespace: str, key: str, entry: MetricsDBEntry) -> None: + if namespace not in self._db: + self._db[namespace] = {} # type = Dict[str, MetricsDBEntry] + if key in self._db[namespace]: raise ValueError("Duplicate entry in MetricsDB: {}".format(key)) else: - self._db[key] = entry + self._db[namespace][key] = entry + - def get_entry(self, key: str) -> MetricsDBEntry: - if key in self._db: - return self._db[key] + def get_entry(self, namespace: str, key: str) -> MetricsDBEntry: + if namespace in self._db: + if key in self._db[namespace]: + return self._db[namespace][key] + else: + raise ValueError("Entry not found in MetricsDB: {}".format(key)) else: - raise ValueError("Entry not found in MetricsDB: {}".format(key)) + raise ValueError("Namespace not found in MetricsDB: {}".format(namespace)) - @property - def entries(self) -> Dict[str, MetricsDBEntry]: - return self._db + def entries(self, namespace: str) -> Dict[str, MetricsDBEntry]: + if namespace in self._db: + return self._db[namespace] + else: + raise ValueError("Namespace not found in MetricsDB: {}".format(namespace)) @property def module_tree(self) -> ModuleTree: @@ -232,25 +221,25 @@ def create_metrics_db_from_ir(self, ir: Union[str, TextIO]) -> MetricsDB: assert(isinstance(y, dict)) # create a db db = MetricsDB() - if self.namespace in y: - testcases = y[self.namespace] + for namespace in y: + testcases = y[namespace] for testcase in testcases: - key = "{}.{}".format(self.namespace, testcase) + key = "{}.{}".format(namespace, testcase) testcase_data = testcases[testcase] if "type" not in testcase_data: raise ValueError("Missing \"type\" field in testcase {}".format(testcase)) mtype = testcase_data["type"] # type: str if mtype in FromIRMap: entry = FromIRMap[mtype](testcase_data) # type: MetricsDBEntry - db.create_entry(key, entry) + db.create_entry(namespace, key, entry) else: raise ValueError("Metric IR field <{}> is not supported. Did you forget to update FromIRMap?".format(mtype)) return db def generate_metric_requests_from_db(self, db: MetricsDB) -> List[str]: output = [] # type: List[str] - for key in db.entries: - entry = db.get_entry(key) + for key in db.entries(self.namespace): + entry = db.get_entry(self.namespace, key) if self._is_supported(entry): output.extend(self._support_map[entry.__class__.__name__](key, entry)) return output @@ -274,7 +263,7 @@ class HasAreaMetricSupport(HasMetricSupport): @property def _support_map(self) -> SupportMap: - x = copy.deepcopy(super()._support_map) # type: SupportMap + x = copy.copy(super()._support_map) # type: SupportMap x.update({ 'ModuleAreaEntry': self.get_module_area }) @@ -288,10 +277,9 @@ class HasTimingPathMetricSupport(HasMetricSupport): @property def _support_map(self) -> SupportMap: - x = copy.deepcopy(super()._support_map) # type: SupportMap + x = copy.copy(super()._support_map) # type: SupportMap x.update({ - 'CriticalPathEntry': self.get_critical_path, - 'TimingPathEntry': self.get_timing_path + 'CriticalPathEntry': self.get_critical_path }) return x @@ -299,6 +287,3 @@ def _support_map(self) -> SupportMap: def get_critical_path(self, key: str, entry: CriticalPathEntry) -> List[str]: pass - @abstractmethod - def get_timing_path(self, key: str, entry: TimingPathEntry) -> List[str]: - pass From 0052d0974c00ee9ca4c55bd11aaf06463f0082c3 Mon Sep 17 00:00:00 2001 From: John Wright Date: Thu, 18 Apr 2019 14:08:51 -0700 Subject: [PATCH 10/10] Now produces output yaml --- src/hammer-vlsi/hammer_utils/__init__.py | 12 +++ src/hammer-vlsi/hammer_vlsi/hammer_metrics.py | 80 +++++++++++++++++-- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/src/hammer-vlsi/hammer_utils/__init__.py b/src/hammer-vlsi/hammer_utils/__init__.py index e72d5f598..e529c6f41 100644 --- a/src/hammer-vlsi/hammer_utils/__init__.py +++ b/src/hammer-vlsi/hammer_utils/__init__.py @@ -7,6 +7,8 @@ import copy import inspect +import os +import errno from functools import reduce from typing import List, Any, Set, Dict, Tuple, TypeVar, Callable, Iterable, Optional from enum import Enum, unique @@ -346,3 +348,13 @@ def get_filetype(filename: str) -> HammerFiletype: return HammerFiletype.VERILOG else: raise NotImplementedError("Unknown file extension: {e}. Please update {f}!".format(e=extension, f=__file__)) + + +def mkdir_p(filepath: str) -> None: + try: + os.makedirs(filepath) + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(filepath): + pass + else: + raise diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py index b9a6c5dc2..15dce1b12 100644 --- a/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py +++ b/src/hammer-vlsi/hammer_vlsi/hammer_metrics.py @@ -7,7 +7,7 @@ # # See LICENSE for licence details. -from hammer_utils import add_dicts +from hammer_utils import add_dicts, get_or_else from hammer_vlsi import HammerTool from abc import abstractmethod from typing import NamedTuple, Optional, List, Any, Dict, Callable, Union, TextIO @@ -66,6 +66,14 @@ class MetricsDBEntry: def register(self, db: 'MetricsDB') -> None: pass + @abstractmethod + def update(self, d: Dict[str, Any]) -> 'MetricsDBEntry': + pass + + @abstractmethod + def to_dict(self) -> Dict[str, Any]: + pass + class CriticalPathEntry(NamedTuple('CriticalPathEntry', [ ('module', ModuleSpec), ('clock', Optional[PortSpec]), # TODO make this connect to HammerIR clock entry somehow (HammerClockSpec??) @@ -89,9 +97,27 @@ def from_ir(ir: IRType) -> 'CriticalPathEntry': except: raise ValueError("Invalid IR for CriticalPathEntry: {}".format(ir)) + @staticmethod + def type_string() -> str: + return 'critical path' + + def to_dict(self) -> Dict[str, Any]: + d = {} + d['type'] = self.__class__.type_string() + d['module'] = self.module.to_str + d['clock'] = self.clock.to_str + d['target'] = str(get_or_else(self.target, "null")) + d['value'] = str(get_or_else(self.value, "null")) + return d + def register(self, db: 'MetricsDB') -> None: db.module_tree.add_module(self.module) + def update(self, d: Dict[str, Any]) -> MetricsDBEntry: + target = d['target'] if 'target' in d else self.target + value = d['value'] if 'value' in d else self.value + return CriticalPathEntry(self.module, self.clock, target, value) + class ModuleAreaEntry(NamedTuple('ModuleAreaEntry', [ ('module', ModuleSpec), ('value', Optional[float]) @@ -109,17 +135,32 @@ def from_ir(ir: IRType) -> 'ModuleAreaEntry': except: raise ValueError("Invalid IR for ModuleAreaEntry: {}".format(ir)) + @staticmethod + def type_string() -> str: + return 'area' + + def to_dict(self) -> Dict[str, Any]: + d = {} + d['type'] = self.__class__.type_string() + d['module'] = self.module.to_str + d['value'] = str(get_or_else(self.value, "null")) + return d + def register(self, db: 'MetricsDB') -> None: db.module_tree.add_module(self.module) + def update(self, d: Dict[str, Any]) -> MetricsDBEntry: + value = d['value'] if 'value' in d else self.value + return ModuleAreaEntry(self.module, value) + # TODO document this #MetricsDBEntry = Union[CriticalPathEntry, ModuleAreaEntry] #SupportMap = Dict[str, Callable[[str, MetricsDBEntry], List[str]]] SupportMap = Dict[str, Callable[[str, Any], List[str]]] FromIRMap = { - "critical path": CriticalPathEntry.from_ir, - "area": ModuleAreaEntry.from_ir + CriticalPathEntry.type_string(): CriticalPathEntry.from_ir, + ModuleAreaEntry.type_string(): ModuleAreaEntry.from_ir } # type: Dict[str, Callable[[IRType], MetricsDBEntry]] class ModuleTree: @@ -184,7 +225,6 @@ def create_entry(self, namespace: str, key: str, entry: MetricsDBEntry) -> None: else: self._db[namespace][key] = entry - def get_entry(self, namespace: str, key: str) -> MetricsDBEntry: if namespace in self._db: if key in self._db[namespace]: @@ -194,12 +234,32 @@ def get_entry(self, namespace: str, key: str) -> MetricsDBEntry: else: raise ValueError("Namespace not found in MetricsDB: {}".format(namespace)) + def update_entry(self, namespace: str, key: str, d: Dict[str, Any]) -> MetricsDBEntry: + if namespace in self._db: + if key in self._db[namespace]: + self._db[namespace][key] = self._db[namespace][key].update(d) + return self._db[namespace][key] + else: + raise ValueError("Entry not found in MetricsDB: {}".format(key)) + else: + raise ValueError("Namespace not found in MetricsDB: {}".format(namespace)) + + def entries(self, namespace: str) -> Dict[str, MetricsDBEntry]: if namespace in self._db: return self._db[namespace] else: raise ValueError("Namespace not found in MetricsDB: {}".format(namespace)) + def serialize(self) -> str: + d = {} + for namespace in self._db: + d[namespace] = {} + for testcase in self._db[namespace]: + d[namespace][testcase] = self._db[namespace][testcase].to_dict() + return yaml.dump(d) + + @property def module_tree(self) -> ModuleTree: return self._tree @@ -224,14 +284,13 @@ def create_metrics_db_from_ir(self, ir: Union[str, TextIO]) -> MetricsDB: for namespace in y: testcases = y[namespace] for testcase in testcases: - key = "{}.{}".format(namespace, testcase) testcase_data = testcases[testcase] if "type" not in testcase_data: raise ValueError("Missing \"type\" field in testcase {}".format(testcase)) mtype = testcase_data["type"] # type: str if mtype in FromIRMap: entry = FromIRMap[mtype](testcase_data) # type: MetricsDBEntry - db.create_entry(namespace, key, entry) + db.create_entry(namespace, testcase, entry) else: raise ValueError("Metric IR field <{}> is not supported. Did you forget to update FromIRMap?".format(mtype)) return db @@ -245,7 +304,9 @@ def generate_metric_requests_from_db(self, db: MetricsDB) -> List[str]: return output def generate_metric_requests_from_ir(self, ir: Union[str, TextIO]) -> List[str]: - return self.generate_metric_requests_from_db(self.create_metrics_db_from_ir(ir)) + # TODO initialize this elsewhere + self.metrics_db = self.create_metrics_db_from_ir(ir) + return self.generate_metric_requests_from_db(self.metrics_db) def generate_metric_requests_from_file(self, filename: str) -> List[str]: if not os.path.isfile(filename): @@ -259,6 +320,11 @@ def generate_metric_requests_from_file(self, filename: str) -> List[str]: def namespace(self) -> str: pass + def read_results_into_db(self, d: Dict[str, Any]) -> str: + for testcase in d: + testcase_data = d[testcase] + self.metrics_db.update_entry(self.namespace, testcase, testcase_data) + class HasAreaMetricSupport(HasMetricSupport): @property