Repository: ucb-bar/hammer (language: python)

Issue:
Formal & Power Tools
Reopening #295 

Addresses #142 


Discussion / hints:
Step `Unit tests (python3.7)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.003s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
/buildbot/run_unittests/build/src/tools/pyyaml/lib3/yaml/constructor.py:126: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  if not isinstance(key, collections.Hashable):
................................
----------------------------------------------------------------------
Ran 32 tests in 0.053s

OK

```
Step `Unit tests (python3.6)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.004s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK
................................
----------------------------------------------------------------------
Ran 32 tests in 0.045s

OK

```
Step `Unit tests (python3.5)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.003s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
................................
----------------------------------------------------------------------
Ran 32 tests in 0.052s

OK

```
Step `Unit tests (python3.4)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.003s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.003s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
................................
----------------------------------------------------------------------
Ran 32 tests in 0.047s

OK

```
Step `Unit tests (python3.3)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 21, in <module>
    from .driver import *
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py", line 24
    HammerFormalLECTool, HammerPowerTool
                      ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.004s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK
................................
----------------------------------------------------------------------
Ran 32 tests in 0.049s

OK

```
Step `Static type checker` failed with details below:
```
Running mypy --package hammer_vlsi
Running mypy --package hammer_utils
Running mypy ../hammer-shell/get-config
Running mypy ../hammer-vlsi/cli_driver_test.py ../hammer-vlsi/constraints_test.py ../hammer-vlsi/lef_utils_test.py ../hammer-vlsi/tech_test.py ../hammer-vlsi/test.py ../hammer-vlsi/units_test.py ../hammer-vlsi/utils_test.py ../hammer-vlsi/verilog_utils_test.py
Running mypy ../hammer_config_test/test.py
Running mypy ../hammer-vlsi/synthesis/mocksynth/__init__.py
Running mypy ../hammer-vlsi/synthesis/vivado/__init__.py
Running mypy ../hammer-vlsi/synthesis/nop.py
Running mypy ../hammer-vlsi/par/nop.py
Running mypy ../hammer-vlsi/par/vivado/__init__.py
Running mypy ../hammer-vlsi/drc/mockdrc.py ../hammer-vlsi/drc/nop.py
Running mypy ../hammer-vlsi/lvs/mocklvs.py ../hammer-vlsi/lvs/nop.py
../hammer-vlsi/hammer_vlsi/driver.py:24: error: invalid syntax
/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py:184: error: invalid syntax
/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py:24: error: invalid syntax

```
Step `Check generate_properties` failed with details below:
```
generate_properties.py is inconsistent with the generated files
--- ../hammer-vlsi/hammer_vlsi/hammer_vlsi_impl.py	2019-03-04 22:55:28.409464048 +0000
+++ _tmp_vlsi.txt	2019-03-04 22:55:48.229323638 +0000
@@ -332,6 +332,7 @@
             raise TypeError("output_ilms must be a List[ILMStruct]")
         self.attr_setter("_output_ilms", value)
 
+
     @property
     def output_gds(self) -> str:
         """

```
Step `Unit tests (python3.7)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.003s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
/buildbot/run_unittests/build/src/tools/pyyaml/lib3/yaml/constructor.py:126: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  if not isinstance(key, collections.Hashable):
................................
----------------------------------------------------------------------
Ran 32 tests in 0.041s

OK

```
Step `Unit tests (python3.6)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.004s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK
................................
----------------------------------------------------------------------
Ran 32 tests in 0.044s

OK

```
Step `Unit tests (python3.5)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.005s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.003s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
................................
----------------------------------------------------------------------
Ran 32 tests in 0.051s

OK

```
Step `Unit tests (python3.4)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.003s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.003s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
................................
----------------------------------------------------------------------
Ran 32 tests in 0.050s

OK

```
Step `Unit tests (python3.3)` failed with details below:
```
Traceback (most recent call last):
  File "../hammer-vlsi/test.py", line 19, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/tech_test.py", line 13, in <module>
    from hammer_vlsi import HammerVLSISettings
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/constraints_test.py", line 10, in <module>
    from hammer_vlsi import DelayConstraint
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
Traceback (most recent call last):
  File "../hammer-vlsi/cli_driver_test.py", line 17, in <module>
    from hammer_vlsi import CLIDriver, HammerDriver, PlacementConstraint, PlacementConstraintType
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s

OK
Traceback (most recent call last):
  File "../hammer-vlsi/units_test.py", line 10, in <module>
    import hammer_vlsi
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/__init__.py", line 23, in <module>
    from .cli_driver import CLIDriver
  File "/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py", line 184
    "formal": self.formal_action,
           ^
SyntaxError: invalid syntax
..
----------------------------------------------------------------------
Ran 2 tests in 0.004s

OK
.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK
................................
----------------------------------------------------------------------
Ran 32 tests in 0.051s

OK

```
Step `Static type checker` failed with details below:
```
Running mypy --package hammer_vlsi
Running mypy --package hammer_utils
Running mypy ../hammer-shell/get-config
Running mypy ../hammer-vlsi/cli_driver_test.py ../hammer-vlsi/constraints_test.py ../hammer-vlsi/lef_utils_test.py ../hammer-vlsi/tech_test.py ../hammer-vlsi/test.py ../hammer-vlsi/units_test.py ../hammer-vlsi/utils_test.py ../hammer-vlsi/verilog_utils_test.py
Running mypy ../hammer_config_test/test.py
Running mypy ../hammer-vlsi/synthesis/mocksynth/__init__.py
Running mypy ../hammer-vlsi/synthesis/vivado/__init__.py
Running mypy ../hammer-vlsi/synthesis/nop.py
Running mypy ../hammer-vlsi/par/nop.py
Running mypy ../hammer-vlsi/par/vivado/__init__.py
Running mypy ../hammer-vlsi/drc/mockdrc.py ../hammer-vlsi/drc/nop.py
Running mypy ../hammer-vlsi/lvs/mocklvs.py ../hammer-vlsi/lvs/nop.py
../hammer-vlsi/hammer_vlsi/cli_driver.py:184: error: invalid syntax
/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/cli_driver.py:184: error: invalid syntax

```
Step `Check generate_properties` failed with details below:
```
generate_properties.py is inconsistent with the generated files
--- ../hammer-vlsi/hammer_vlsi/hammer_vlsi_impl.py	2019-03-04 22:55:28.409464048 +0000
+++ _tmp_vlsi.txt	2019-03-04 23:03:20.398144742 +0000
@@ -332,6 +332,7 @@
             raise TypeError("output_ilms must be a List[ILMStruct]")
         self.attr_setter("_output_ilms", value)
 
+
     @property
     def output_gds(self) -> str:
         """

```
Step `Check generate_properties` failed with details below:
```
generate_properties.py is inconsistent with the generated files
--- ../hammer-vlsi/hammer_vlsi/hammer_vlsi_impl.py	2019-03-04 22:55:28.409464048 +0000
+++ _tmp_vlsi.txt	2019-03-04 23:06:14.184931236 +0000
@@ -332,6 +332,7 @@
             raise TypeError("output_ilms must be a List[ILMStruct]")
         self.attr_setter("_output_ilms", value)
 
+
     @property
     def output_gds(self) -> str:
         """

```
Step `Static type checker` failed with details below:
```
Running mypy --package hammer_vlsi
Running mypy --package hammer_utils
Running mypy ../hammer-shell/get-config
Running mypy ../hammer-vlsi/cli_driver_test.py ../hammer-vlsi/constraints_test.py ../hammer-vlsi/lef_utils_test.py ../hammer-vlsi/tech_test.py ../hammer-vlsi/test.py ../hammer-vlsi/units_test.py ../hammer-vlsi/utils_test.py ../hammer-vlsi/verilog_utils_test.py
Running mypy ../hammer_config_test/test.py
Running mypy ../hammer-vlsi/synthesis/mocksynth/__init__.py
Running mypy ../hammer-vlsi/synthesis/vivado/__init__.py
Running mypy ../hammer-vlsi/synthesis/nop.py
Running mypy ../hammer-vlsi/par/nop.py
Running mypy ../hammer-vlsi/par/vivado/__init__.py
Running mypy ../hammer-vlsi/drc/mockdrc.py ../hammer-vlsi/drc/nop.py
Running mypy ../hammer-vlsi/lvs/mocklvs.py ../hammer-vlsi/lvs/nop.py
../hammer-vlsi/hammer_vlsi/driver.py:447: error: "HammerFormalLECTool" has no attribute "reference"
../hammer-vlsi/hammer_vlsi/driver.py:448: error: "HammerFormalLECTool" has no attribute "reference"
../hammer-vlsi/hammer_vlsi/driver.py:451: error: "HammerFormalLECTool" has no attribute "implementation"
../hammer-vlsi/hammer_vlsi/driver.py:452: error: "HammerFormalLECTool" has no attribute "implementation"
/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py:447: error: "HammerFormalLECTool" has no attribute "reference"
/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py:448: error: "HammerFormalLECTool" has no attribute "reference"
/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py:451: error: "HammerFormalLECTool" has no attribute "implementation"
/buildbot/run_unittests/build/src/hammer-vlsi/hammer_vlsi/driver.py:452: error: "HammerFormalLECTool" has no attribute "implementation"

```

Fix patch:
diff --git a/doc/Hammer-Flow/Formal.rst b/doc/Hammer-Flow/Formal.rst
new file mode 100644
index 000000000..0d33f7400
--- /dev/null
+++ b/doc/Hammer-Flow/Formal.rst
@@ -0,0 +1,67 @@
+Formal Verification
+===============================
+
+Hammer supports post-synthesis and post-P&R formal verification. It provides a simple API to provide a set of reference and implementation inputs (e.g. Verilog netlists) to perform formal verification checks such as logical equivalence checking (LEC).
+This action requires a tool plugin to implement ``HammerFormalTool``.
+
+Formal Verification Setup Keys
+-------------------------------
+
+* Namespace: ``vlsi.core``
+
+    * ``formal_tool_path``
+        * Set to the directory containing the tool plugin directory for the formal tool, typically ``/path/to/tool_plugin/formal``. This will be the parent directory of the directory containing ``__init__.py`` and ``defaults.yml``.
+    * ``formal_tool``
+        * Actual name of the formal verification tool that is setup in the directory ``formal_tool_path``, e.g. ``conformal``
+
+Formal Verification Input Keys
+-------------------------------
+
+* Namespace: ``formal.inputs``
+
+    * ``check`` (str)
+
+        * Name of the formal verification check that is to be performed. Support varies based on the specific tool plugin. Potential check types/algorithms could be "lec", "power", "constraint", "cdc", "property", "eco", and more. At the moment, only "lec" is supported.
+
+    * ``input_files`` ([])
+
+        * A list of file paths to implementation source files (verilog, vhdl, spice, liberty, etc.) to be passed to the formal verification tool. For a LEC tool, this would be the sources for the "revised" design. The paths may be relative to the directory in which ``hammer-vlsi`` is called.
+
+    * ``reference_files`` ([])
+
+        * A list of file paths to reference source files (verilog, vhdl, spice, liberty, etc.) to be passed to the formal verification tool. For a LEC tool, this would be the sources for the "golden" design. The paths may be relative to the directory in which ``hammer-vlsi`` is called.
+
+    * ``top_module`` (str)
+
+        * Name of the top level module of the design to be verified.
+
+Formal Verification Inputs
+-------------------------------
+
+Running the ``syn-to-formal`` action after running synthesis will automatically generate the Hammer IR required to pipe the synthesis outputs to the Hammer formal verification tool, and should be included in the Hammer call, as demonstrated in the "Post-Synthesis Formal Verification" command below.  The same goes for post-place-and-route formal verification.
+
+At this time, only netlists are passed to the formal verification tool. Additional files (SDCs, CPFs, etc.) are needed for more advanced formal verification checks but are not yet currently supported. Similarly, formal verification on the behavioral RTL is also not yet supported.
+
+Formal Verification Outputs
+-------------------------------
+
+The formal verification tool produces reports in ``OBJ_DIR/formal-rundir/``. Outputs from formal verification flows such as engineering change order (ECO) patches are not yet supported.
+
+Formal Verification Commands
+-------------------------------
+
+* Synthesis to Formal Verification
+
+    * ``hammer-vlsi -e env.yml -p config.yml -p OBJ_DIR/syn-rundir/syn-output.json -o OBJ_DIR/syn-to-formal_input.json --obj_dir OBJ_DIR syn-to-formal``
+
+* Post-Synthesis Formal Verification
+
+    * ``hammer-vlsi -e env.yml -p config.yml -p OBJ_DIR/syn-to-formal_input.json --obj_dir OBJ_DIR formal``
+
+* P&R to Formal Verification 
+
+    * ``hammer-vlsi -e env.yml -p config.yml -p OBJ_DIR/par-rundir/par-output.json -o OBJ_DIR/par-to-formal_input.json --obj_dir OBJ_DIR par-to-formal``
+
+* Post-P&R Formal Verification
+
+    * ``hammer-vlsi -e env.yml -p config.yml -p OBJ_DIR/par-to-formal_input.json --obj_dir OBJ_DIR formal``
diff --git a/doc/Hammer-Flow/index.rst b/doc/Hammer-Flow/index.rst
index 9fe6949fc..0b9ea66ee 100644
--- a/doc/Hammer-Flow/index.rst
+++ b/doc/Hammer-Flow/index.rst
@@ -14,3 +14,4 @@ This documentation will walk through the currently supported steps of the Hammer
    LVS
    Simulation
    Power
+   Formal
diff --git a/src/hammer-tech/hammer_tech.py b/src/hammer-tech/hammer_tech.py
index b3dc3fc5a..5861ddceb 100644
--- a/src/hammer-tech/hammer_tech.py
+++ b/src/hammer-tech/hammer_tech.py
@@ -361,7 +361,7 @@ def cache_dir(self, value: str) -> None:
         self._cachedir = value  # type: str
         # Ensure the cache_dir exists.
         os.makedirs(value, mode=0o700, exist_ok=True)
-    
+
     # @classmethod
     def expand_tech_cache_path(self, path) -> str:
         """ Replace occurrences of the cache directory's basename with
@@ -1105,6 +1105,13 @@ def get_tech_power_hooks(self, tool_name: str) -> List['HammerToolHookAction']:
         """
         return list()
 
+    def get_tech_formal_hooks(self, tool_name: str) -> List['HammerToolHookAction']:
+        """
+        Return a list of formal hooks for this technology and tool.
+        To be overridden by subclasses.
+        """
+        return list()
+
     def get_tech_pcb_hooks(self, tool_name: str) -> List['HammerToolHookAction']:
         """
         Return a list of pcb hooks for this technology and tool.
diff --git a/src/hammer-vlsi/defaults.yml b/src/hammer-vlsi/defaults.yml
index 6743578b6..0ec948a8e 100644
--- a/src/hammer-vlsi/defaults.yml
+++ b/src/hammer-vlsi/defaults.yml
@@ -55,6 +55,11 @@ vlsi.core:
   power_tool_path: ["${vlsi.builtins.hammer_vlsi_path/power"]
   power_tool_path_meta: subst
 
+  # Path(s) to formal tools.
+  # Add the built-in path to power tools
+  formal_tool_path: ["${vlsi.builtins.hammer_vlsi_path/formal"]
+  formal_tool_path_meta: subst
+
   # Path(s) to PCB deliverable tools.
   # Add the built-in path to PCB deliverable tools.
   pcb_tool_path: ["${vlsi.builtins.hammer_vlsi_path}/pcb"]
@@ -82,6 +87,9 @@ vlsi.core:
   # Power analysis tool to use.
   power_tool: "nop"
 
+  # Formal verification/check tool to use.
+  formal_tool: "nop"
+
   # PCB deliverable tool to use.
   # PCB deliverables include footprints, symbols, etc.
   # For the exact deliverables produced, please see the deliverable tool's documentation.
@@ -970,6 +978,41 @@ power.submit:
   settings: "vlsi.submit.settings"
   settings_meta: lazycrossref
 
+# Specific inputs for the formal tool.
+# These may vary based on the specific tool plugin.
+# For example, files could be combinations of Verilog, Liberty, Spice, CPF, and/or SDC files.
+formal.inputs:
+  # Check type to run. Support varies based on the specific tool plugin.
+  # Potential options (see tool plugin defaults.yml for supported checks): 
+  # - lec: logical equivalence checking (supported)
+  # - power: power intent (CPF/UPF) and structure checking (not yet supported)
+  # - constraint: design constaint (SDC) checking (not yet supported)
+  # - cdc: clock domain crossing checking (not yet supported)
+  # - property: contention, tristate, don't care, etc. checking (not yet supported)
+  # - eco: engineering change order generation (not yet supported)
+  # type: str
+  check: "lec"
+
+  # Input files.
+  # Typically a list of Verilog/VHDL files, depending on the formal tool.
+  # For equivalence checking, this is commonly referred to as the "revised" design.
+  # type: List[str]
+  input_files: []
+
+  # The reference (golden) design files for equivalence checks
+  # type: List[str]
+  reference_files: []
+
+  # Top RTL module for running verification on
+  # type: str
+  top_module: null
+
+formal.submit:
+  command: "${vlsi.submit.command}"
+  command_meta: lazysubst
+  settings: "vlsi.submit.settings"
+  settings_meta: lazycrossref
+
 technology.pcb:
   # Desired pad opening diameter (in post-shrink um)
   # Typically this should be defined in the technology plugin, assuming everyone will use the same pad and board strategy
diff --git a/src/hammer-vlsi/generate_properties.py b/src/hammer-vlsi/generate_properties.py
index da920a747..4ddcd7c49 100755
--- a/src/hammer-vlsi/generate_properties.py
+++ b/src/hammer-vlsi/generate_properties.py
@@ -217,6 +217,21 @@ def main(args) -> int:
                                 ],
                                 outputs=[]
                                 )
+    HammerFormalTool = Interface(module="HammerFormalTool",
+                            filename="hammer_vlsi/hammer_vlsi_impl.py",
+                            inputs=[
+                                InterfaceVar("check", "str",
+                                    "formal verification check type to run"),
+                                InterfaceVar("input_files", "List[str]",
+                                    "input collection of implementation design files"),
+                                InterfaceVar("reference_files", "List[str]",
+                                    "input collection of reference design files"),
+                                InterfaceVar("top_module", "str", "top RTL module"),
+                                InterfaceVar("post_synth_sdc", "Optional[str]",
+                                    "(optional) input post-synthesis SDC constraint file")
+                            ],
+                            outputs=[]
+                            )
     HammerPCBDeliverableTool = Interface(module="HammerPCBDeliverableTool",
                                        filename="hammer_vlsi/hammer_vlsi_impl.py",
                                        inputs=[],
@@ -238,6 +253,7 @@ def main(args) -> int:
     generate_interface(HammerSRAMGeneratorTool)
     generate_interface(HammerSimTool)
     generate_interface(HammerPowerTool)
+    generate_interface(HammerFormalTool)
     generate_interface(HammerPCBDeliverableTool)
 
     if selected_file == "":
diff --git a/src/hammer-vlsi/hammer_vlsi/cli_driver.py b/src/hammer-vlsi/hammer_vlsi/cli_driver.py
index dfe04496c..8e0960b2e 100644
--- a/src/hammer-vlsi/hammer_vlsi/cli_driver.py
+++ b/src/hammer-vlsi/hammer_vlsi/cli_driver.py
@@ -118,6 +118,7 @@ def __init__(self) -> None:
         self.sram_generator_rundir = ""  # type: Optional[str]
         self.sim_rundir = ""  # type: Optional[str]
         self.power_rundir = "" # type: Optional[str]
+        self.formal_rundir = "" # type: Optional[str]
         self.pcb_rundir = ""  # type: Optional[str]
 
         # If a subclass has defined these, don't clobber them in init
@@ -167,6 +168,10 @@ def __init__(self) -> None:
             check_CLIActionType_type(self.power_action) # type: ignore
         else:
             self.power_action = self.create_power_action([]) # type: CLIActionConfigType
+        if hasattr(self, "formal_action"):
+            check_CLIActionType_type(self.formal_action) # type: ignore
+        else:
+            self.formal_action = self.create_formal_action([]) # type: CLIActionConfigType
 
         # Dictionaries of module-CLIActionConfigType for hierarchical flows.
         # See all_hierarchical_actions() below.
@@ -177,6 +182,7 @@ def __init__(self) -> None:
         self.hierarchical_lvs_actions = {}  # type: Dict[str, CLIActionConfigType]
         self.hierarchical_sim_actions = {}  # type: Dict[str, CLIActionConfigType]
         self.hierarchical_power_actions = {}  # type: Dict[str, CLIActionConfigType]
+        self.hierarchical_formal_actions = {}  # type: Dict[str, CLIActionConfigType]
         self.hierarchical_auto_action = None  # type: Optional[CLIActionConfigType]
 
     def action_map(self) -> Dict[str, CLIActionType]:
@@ -228,7 +234,14 @@ def action_map(self) -> Dict[str, CLIActionType]:
             "par-to-power": self.par_to_power_action,
             "par_to_power": self.par_to_power_action,
             "sim-to-power": self.sim_to_power_action,
-            "sim_to_power": self.sim_to_power_action
+            "sim_to_power": self.sim_to_power_action,
+            "formal": self.formal_action,
+            "synthesis_to_formal": self.synthesis_to_formal_action,
+            "synthesis-to-formal": self.synthesis_to_formal_action,
+            "syn_to_formal": self.synthesis_to_formal_action,
+            "syn-to-formal": self.synthesis_to_formal_action,
+            "par_to_formal": self.par_to_formal_action,
+            "par-to-formal": self.par_to_formal_action
         }, self.all_hierarchical_actions)
 
     @staticmethod
@@ -296,6 +309,13 @@ def get_extra_power_hooks(self) -> List[HammerToolHookAction]:
         """
         return list()
 
+    def get_extra_formal_hooks(self) -> List[HammerToolHookAction]:
+        """
+        Return a list of extra formal hooks in this project.
+        To be overridden by subclasses.
+        """
+        return list()
+
     def get_extra_pcb_hooks(self) -> List[HammerToolHookAction]:
         """
         Return a list of extra PCB deliverable hooks in this project.
@@ -377,6 +397,14 @@ def create_power_action(self, custom_hooks: List[HammerToolHookAction],
         return self.create_action("power", hooks if len(hooks) > 0 else None,
                                   pre_action_func, post_load_func, post_run_func)
 
+    def create_formal_action(self, custom_hooks: List[HammerToolHookAction],
+                          pre_action_func: Optional[Callable[[HammerDriver], None]] = None,
+                          post_load_func: Optional[Callable[[HammerDriver], None]] = None,
+                          post_run_func: Optional[Callable[[HammerDriver], None]] = None) -> CLIActionConfigType:
+        hooks = self.get_extra_formal_hooks() + custom_hooks  # type: List[HammerToolHookAction]
+        return self.create_action("formal", hooks if len(hooks) > 0 else None,
+                                  pre_action_func, post_load_func, post_run_func)
+
     def create_sram_generator_action(self, custom_hooks: List[HammerToolHookAction],
                           pre_action_func: Optional[Callable[[HammerDriver], None]] = None,
                           post_load_func: Optional[Callable[[HammerDriver], None]] = None,
@@ -543,6 +571,23 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op
                 dump_config_to_json_file(os.path.join(driver.power_tool.run_dir, "power-output-full.json"),
                                          self.get_full_config(driver, output))
                 post_run_func_checked(driver)
+            elif action_type == "formal":
+                if not driver.load_formal_tool(get_or_else(self.formal_rundir, "")):
+                    return None
+                else:
+                    post_load_func_checked(driver)
+                assert driver.formal_tool is not None, "load_formal_tool was unsuccessful"
+                success, output = driver.run_formal(
+                        driver.formal_tool.get_tool_hooks() + \
+                        driver.tech.get_tech_formal_hooks(driver.formal_tool.name) + \
+                        list(extra_hooks or []))
+                if not success:
+                    driver.log.error("Formal tool did not succeed")
+                    return None
+                dump_config_to_json_file(os.path.join(driver.formal_tool.run_dir, "formal-output.json"), output)
+                dump_config_to_json_file(os.path.join(driver.formal_tool.run_dir, "formal-output-full.json"),
+                                         self.get_full_config(driver, output))
+                post_run_func_checked(driver)
             elif action_type == "pcb":
                 if not driver.load_pcb_tool(get_or_else(self.pcb_rundir, "")):
                     return None
@@ -639,6 +684,24 @@ def sim_to_power_action(self, driver: HammerDriver, append_error_func: Callable[
         else:
             return self.get_full_config(driver, power_input_only)
 
+    def synthesis_to_formal_action(self, driver: HammerDriver, append_error_func: Callable[[str], None]) -> Optional[dict]:
+        """Create a full config to run the output."""
+        formal_input_only = HammerDriver.synthesis_output_to_formal_input(driver.project_config)
+        if formal_input_only is None:
+            driver.log.error("Input config does not appear to contain valid synthesis outputs")
+            return None
+        else:
+            return self.get_full_config(driver, formal_input_only)
+
+    def par_to_formal_action(self, driver: HammerDriver, append_error_func: Callable[[str], None]) -> Optional[dict]:
+        """Create a full config to run the output."""
+        formal_input_only = HammerDriver.par_output_to_formal_input(driver.project_config)
+        if formal_input_only is None:
+            driver.log.error("Input config does not appear to contain valid par outputs")
+            return None
+        else:
+            return self.get_full_config(driver, formal_input_only)
+
 
     def create_synthesis_par_action(self, synthesis_action: CLIActionConfigType, par_action: CLIActionConfigType) -> CLIActionConfigType:
         """
@@ -803,6 +866,12 @@ def add_variants(templates: List[str], block: str, action: CLIActionConfigType)
                 "power_{block}"
             ], module, action)
 
+        for module, action in self.hierarchical_formal_actions.items():
+            add_variants([
+                "formal-{block}",
+                "formal_{block}"
+            ], module, action)
+
         return actions
 
     def get_extra_hierarchical_synthesis_hooks(self, driver: HammerDriver) -> Dict[str, List[HammerToolHookAction]]:
@@ -859,6 +928,15 @@ def get_extra_hierarchical_power_hooks(self, driver: HammerDriver) -> Dict[str,
         """
         return dict()
 
+    def get_extra_hierarchical_formal_hooks(self, driver: HammerDriver) -> Dict[str, List[HammerToolHookAction]]:
+        """
+        Return a list of extra hierarchical formal hooks in this project.
+        To be overridden by subclasses.
+
+        :return: Dictionary of (module name, list of hooks)
+        """
+        return dict()
+
     # The following functions are present for further user customizability.
 
     def get_hierarchical_synthesis_action(self, module: str) -> CLIActionConfigType:
@@ -945,6 +1023,18 @@ def get_hierarchical_power_action(self, module: str) -> CLIActionConfigType:
         """
         return self.hierarchical_power_actions[module]
 
+    def set_hierarchical_formal_action(self, module: str, action: CLIActionConfigType) -> None:
+        """
+        Set the action associated with hierarchical formal for the given module (in hierarchical flows).
+        """
+        self.hierarchical_formal_actions[module] = action
+
+    def get_hierarchical_formal_action(self, module: str) -> CLIActionConfigType:
+        """
+        Get the action associated with hierarchical formal for the given module (in hierarchical flows).
+        """
+        return self.hierarchical_formal_actions[module]
+
     def valid_actions(self) -> List[str]:
         """Get the list of valid actions for the command-line driver."""
         return list(self.action_map().keys())
@@ -1027,6 +1117,7 @@ def args_to_driver(self, args: dict,
         self.lvs_rundir = get_nonempty_str(args['lvs_rundir'])
         self.sim_rundir = get_nonempty_str(args['sim_rundir'])
         self.power_rundir = get_nonempty_str(args['power_rundir'])
+        self.formal_rundir = get_nonempty_str(args['formal_rundir'])
 
         # Stage control: from/to
         from_step = get_nonempty_str(args['from_step'])
@@ -1071,6 +1162,9 @@ def args_to_driver(self, args: dict,
             driver.set_post_custom_power_tool_hooks(HammerTool.make_start_stop_hooks(
                 HammerStartStopStep(step=start_step, inclusive=start_incl),
                 HammerStartStopStep(step=stop_step, inclusive=stop_incl)))
+            driver.set_post_custom_formal_tool_hooks(HammerTool.make_start_stop_hooks(
+                HammerStartStopStep(step=start_step, inclusive=start_incl),
+                HammerStartStopStep(step=stop_step, inclusive=stop_incl)))
 
         # Hierarchical support.
         # Generate synthesis and par actions for each module above.
@@ -1127,6 +1221,13 @@ def power_pre_func(d: HammerDriver) -> None:
                     base_project_config[0] = deeplist(driver.project_configs)
                     d.update_project_configs(deeplist(base_project_config[0]) + [config])
 
+                def formal_pre_func(d: HammerDriver) -> None:
+                    self.lvs_rundir = os.path.join(d.obj_dir, "formal-{module}".format(
+                        module=module))  # TODO(edwardw): fix this ugly os.path.join; it doesn't belong here.
+                    # TODO(edwardw): remove ugly hack to store stuff in parent context
+                    base_project_config[0] = deeplist(driver.project_configs)
+                    d.update_project_configs(deeplist(base_project_config[0]) + [config])
+
                 def post_run(d: HammerDriver, rundir: str) -> None:
                     # Write out the configs used/generated for logging/debugging.
                     with open(os.path.join(rundir, "full_config.json"), "w") as f:
@@ -1156,6 +1257,9 @@ def sim_post_run(d: HammerDriver) -> None:
                 def power_post_run(d: HammerDriver) -> None:
                     post_run(d, get_or_else(self.power_rundir, ""))
 
+                def formal_post_run(d: HammerDriver) -> None:
+                    post_run(d, get_or_else(self.formal_rundir, ""))
+
                 syn_action = self.create_synthesis_action(self.get_extra_hierarchical_synthesis_hooks(driver).get(module, []),
                                                           pre_action_func=syn_pre_func, post_load_func=None,
                                                           post_run_func=syn_post_run)
@@ -1182,6 +1286,10 @@ def power_post_run(d: HammerDriver) -> None:
                                                     pre_action_func=power_pre_func, post_load_func=None,
                                                     post_run_func=power_post_run)
                 self.set_hierarchical_power_action(module, power_action)
+                formal_action = self.create_formal_action(self.get_extra_hierarchical_formal_hooks(driver).get(module, []),
+                                                    pre_action_func=formal_pre_func, post_load_func=None,
+                                                    post_run_func=formal_post_run)
+                self.set_hierarchical_formal_action(module, formal_action)
 
             create_actions(module_iter, config_iter)
 
@@ -1318,6 +1426,8 @@ def main(self, args: Optional[List[str]] = None) -> None:
                             help='(optional) Directory to store simulation results in')
         parser.add_argument("--power_rundir", required=False, default="",
                             help='(optional) Directory to store power results in')
+        parser.add_argument("--formal_rundir", required=False, default="",
+                            help='(optional) Directory to store formal results in')
         # Optional arguments for step control.
         parser.add_argument("--start_before_step", "--from_step", dest="from_step", required=False,
                             help="Run the given action from before the given step (inclusive). Not compatible with --start_after_step.")
diff --git a/src/hammer-vlsi/hammer_vlsi/driver.py b/src/hammer-vlsi/hammer_vlsi/driver.py
index 1b327ac97..c0f3356e8 100644
--- a/src/hammer-vlsi/hammer_vlsi/driver.py
+++ b/src/hammer-vlsi/hammer_vlsi/driver.py
@@ -20,7 +20,7 @@
 from .hammer_tool import HammerTool
 from .hooks import HammerToolHookAction
 from .hammer_vlsi_impl import HammerVLSISettings, HammerPlaceAndRouteTool, HammerSynthesisTool, \
-    HammerSignoffTool, HammerDRCTool, HammerLVSTool, HammerSRAMGeneratorTool, HammerPCBDeliverableTool, HammerSimTool, HammerPowerTool, \
+    HammerSignoffTool, HammerDRCTool, HammerLVSTool, HammerSRAMGeneratorTool, HammerPCBDeliverableTool, HammerSimTool, HammerPowerTool, HammerFormalTool, \
     HierarchicalMode, load_tool, PlacementConstraint, SRAMParameters, ILMStruct, SimulationLevel
 from hammer_logging import HammerVLSIFileLogger, HammerVLSILogging, HammerVLSILoggingContext
 from .submit_command import HammerSubmitCommand
@@ -109,6 +109,7 @@ def __init__(self, options: HammerDriverOptions, extra_project_config: dict = {}
         self.sram_generator_tool = None  # type: Optional[HammerSRAMGeneratorTool]
         self.sim_tool = None  # type: Optional[HammerSimTool]
         self.power_tool = None # type: Optional[HammerPowerTool]
+        self.formal_tool = None # type: Optional[HammerFormalTool]
 
         # Initialize tool hooks. Used to specify resume/pause hooks after custom hooks have been registered.
         self.post_custom_syn_tool_hooks = []  # type: List[HammerToolHookAction]
@@ -118,6 +119,7 @@ def __init__(self, options: HammerDriverOptions, extra_project_config: dict = {}
         self.post_custom_sram_generator_tool_hooks = []  # type: List[HammerToolHookAction]
         self.post_custom_sim_tool_hooks = []  # type: List[HammerToolHookAction]
         self.post_custom_power_tool_hooks = [] # type: List[HammerToolHookAction]
+        self.post_custom_formal_tool_hooks = [] # type: List[HammerToolHookAction]
         self.post_custom_pcb_tool_hooks = []  # type: List[HammerToolHookAction]
 
     @property
@@ -593,6 +595,76 @@ def load_power_tool(self, run_dir: str = "") -> bool:
         self.update_tool_configs()
         return True
 
+    def set_up_formal_tool(self, formal_tool: HammerFormalTool,
+                              name: str, run_dir: str = "") -> bool:
+        """
+        Set up and store the given formal tool instance for use in this
+        driver.
+        :param formal_tool: Tool instance.
+        :param name: Short name (e.g. "conformal") of the tool instance. Typically
+                     obtained from the database.
+        :param run_dir: Directory to use for the tool run_dir. Defaults to the
+                        run_dir passed in the HammerDriver constructor.
+        :return: True if setup was successful.
+        """
+
+        if self.tech is None:
+            self.log.error("Must load technology before loading formal tool")
+            return False
+
+        if run_dir == "":
+            run_dir = os.path.join(self.obj_dir, "formal-rundir")
+
+        formal_tool.name = name
+        formal_tool.logger = self.log.context("formal")
+        formal_tool.set_database(self.database)
+        formal_tool.run_dir = run_dir
+        formal_tool.technology = self.tech
+        formal_tool.submit_command = HammerSubmitCommand.get("formal", self.database)
+
+        formal_tool.check = self.database.get_setting("formal.inputs.check")
+        formal_tool.input_files = self.database.get_setting("formal.inputs.input_files")
+        formal_tool.hierarchical_mode = HierarchicalMode.from_str(
+            self.database.get_setting("vlsi.inputs.hierarchical.mode"))
+        # Special case: if non-leaf hierarchical, append ilm sim netlists
+        if formal_tool.hierarchical_mode.is_nonleaf_hierarchical():
+            for ilm in formal_tool.get_input_ilms():
+                if isinstance(ilm.sim_netlist, str):
+                    formal_tool.input_files.append(ilm.sim_netlist)
+        formal_tool.reference_files = self.database.get_setting("formal.inputs.reference_files")
+        formal_tool.top_module = self.database.get_setting("formal.inputs.top_module")
+        missing_inputs = False
+        if formal_tool.check == "":
+            self.log.error("No check specified for formal")
+            missing_inputs = True
+        if len(formal_tool.input_files) == 0:
+            self.log.error("No input files specified for formal")
+            missing_inputs = True
+        if missing_inputs:
+            return False
+
+        self.formal_tool = formal_tool
+
+        self.tool_configs["formal"] = formal_tool.get_config()
+        self.update_tool_configs()
+        return True
+
+    def load_formal_tool(self, run_dir: str = "") -> bool:
+        """
+        Loads a formal tool on a given database
+
+        :param run_dir: Directory to use for the tool run_dir. Defaults to the run_dir passed in the HammerDriver
+                        constructor.
+        :return: True if formal tool loading was successful, False otherwise.
+        """
+        config_result = self.instantiate_tool_from_config("formal", HammerFormalTool)
+        if config_result is None:
+            return False
+        else:
+            (formal_tool, name) = config_result
+            assert isinstance(formal_tool, HammerFormalTool)
+            return self.set_up_formal_tool(formal_tool, name, run_dir)
+
     def load_pcb_tool(self, run_dir: str = "") -> bool:
         """
         Load the PCB deliverable tool based on the given database.
@@ -686,6 +758,15 @@ def set_post_custom_power_tool_hooks(self, hooks: List[HammerToolHookAction]) ->
         """
         self.post_custom_power_tool_hooks = list(hooks)
 
+    def set_post_custom_formal_tool_hooks(self, hooks: List[HammerToolHookAction]) -> None:
+        """
+        Set the extra list of hooks used for control flow (resume/pause) in run_formal.
+        They will run after main/hook_actions.
+
+        :param hooks: Hooks to run
+        """
+        self.post_custom_formal_tool_hooks = list(hooks)
+
     def run_synthesis(self, hook_actions: Optional[List[HammerToolHookAction]] = None, force_override: bool = False) -> \
             Tuple[bool, dict]:
         """
@@ -786,6 +867,32 @@ def synthesis_output_to_sim_input(output_dict: dict) -> Optional[dict]:
             # KeyError means that the given dictionary is missing output keys.
             return None
 
+    @staticmethod
+    def synthesis_output_to_formal_input(output_dict: dict) -> Optional[dict]:
+        """
+        Generate the appropriate inputs for running formal tools from the
+        outputs of synthesis run.
+        Does not merge the results with any project dictionaries.
+        :param output_dict: Dict containing synthesis.outputs.*
+        :return: formal.inputs.* settings generated from output_dict,
+                 or None if output_dict was invalid
+        """
+        try:
+            reference_files = deeplist(output_dict["synthesis.inputs.input_files"])
+            input_files = deeplist(output_dict["synthesis.outputs.output_files"])
+            result = {
+                "formal.inputs.input_files": input_files,
+                "formal.inputs.input_files_meta": "append",
+                "formal.inputs.reference_files": reference_files,
+                "formal.inputs.reference_files_meta": "append",
+                "formal.inputs.top_module": output_dict["synthesis.inputs.top_module"],
+                "vlsi.builtins.is_complete": False
+            }  # type: Dict[str, Any]
+            return result
+        except KeyError:
+            # KeyError means that the given dictionary is missing output keys.
+            return None
+
     @staticmethod
     def par_output_to_sim_input(output_dict: dict) -> Optional[dict]:
         """
@@ -835,6 +942,32 @@ def par_output_to_power_input(output_dict: dict) -> Optional[dict]:
             # KeyError means that the given dictionary is missing output keys.
             return None
 
+    @staticmethod
+    def par_output_to_formal_input(output_dict: dict) -> Optional[dict]:
+        """
+        Generate the appropriate inputs for running formal tools from the
+        outputs of par run.
+        Does not merge the results with any project dictionaries.
+        :param output_dict: Dict containing par.outputs.*
+        :return: formal.inputs.* settings generated from output_dict,
+                 or None if output_dict was invalid
+        """
+        try:
+            reference_files = deeplist(output_dict["par.inputs.input_files"])
+            input_files = deeplist([output_dict["par.outputs.output_sim_netlist"]])
+            result = {
+                "formal.inputs.input_files": input_files,
+                "formal.inputs.input_files_meta": "append",
+                "formal.inputs.reference_files": reference_files,
+                "formal.inputs.reference_files_meta": "append",
+                "formal.inputs.top_module": output_dict["par.inputs.top_module"],
+                "vlsi.builtins.is_complete": False
+            }  # type: Dict[str, Any]
+            return result
+        except KeyError:
+            # KeyError means that the given dictionary is missing output keys.
+            return None
+
     def run_par(self, hook_actions: Optional[List[HammerToolHookAction]] = None, force_override: bool = False) -> Tuple[
         bool, dict]:
         """
@@ -1172,6 +1305,47 @@ def run_power(self, hook_actions: Optional[List[HammerToolHookAction]] = None, f
 
         return run_succeeded, output_config
 
+    def run_formal(self, hook_actions: Optional[List[HammerToolHookAction]] = None, force_override: bool = False) -> \
+            Tuple[bool, dict]:
+        """
+        Run formal verification based on the given database.
+        The output config dict returned does NOT have a copy of the input config settings.
+
+        :param hook_actions: List of hook actions, or leave as None to use the hooks sets in set_formal_hooks.
+                             Hooks from set_formal_hooks, if present, will be appended afterwards.
+        :param force_override: Set to true to overwrite instead of append.
+        :return: Tuple of (success, output config dict)
+        """
+        if self.formal_tool is None:
+            self.log.error("Must load formal tool before calling run_formal")
+            return False, {}
+
+        # TODO: think about artifact storage?
+        self.log.info("Starting formal with tool '%s'" % (self.formal_tool.name))
+        if hook_actions is None:
+            hooks_to_use = self.post_custom_formal_tool_hooks
+        else:
+            if force_override:
+                hooks_to_use = hook_actions
+            else:
+                hooks_to_use = hook_actions + self.post_custom_formal_tool_hooks
+
+        run_succeeded = self.formal_tool.run(hooks_to_use)
+        if not run_succeeded:
+            self.log.error("Formal tool %s failed! Please check its output." % self.formal_tool.name)
+            # Allow the flow to keep running, just in case.
+            # TODO: make this an option
+
+        # Record output from the formal tool into the JSON output.
+        output_config = {}  # type: Dict[str, Any]
+        try:
+            output_config.update(self.formal_tool.export_config_outputs())
+        except ValueError as e:
+            self.log.fatal(e.args[0])
+            return False, {}
+
+        return run_succeeded, output_config
+
     def run_pcb(self, hook_actions: Optional[List[HammerToolHookAction]] = None, force_override: bool = False) -> Tuple[
         bool, dict]:
         """
diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_build_systems.py b/src/hammer-vlsi/hammer_vlsi/hammer_build_systems.py
index b5e9b636e..840e00ca8 100644
--- a/src/hammer-vlsi/hammer_vlsi/hammer_build_systems.py
+++ b/src/hammer-vlsi/hammer_vlsi/hammer_build_systems.py
@@ -40,8 +40,10 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
         - sim-syn
         - sim-par
         - power-par
+        - formal-syn
+        - formal-par
 
-    For hierarchical flows, the syn, par, drc, lvs, sim, and power actions will all be suffixed with the name
+    For hierarchical flows, the syn, par, drc, lvs, sim, power, and formal actions will all be suffixed with the name
     of the hierarchical modules (e.g. syn-Top, syn-SubModA, par-SubModA, etc.). The appropriate
     dependencies and bridge actions are automatically generated from the hierarchy provided in the
     Hammer IR.
@@ -129,7 +131,7 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
         ####################################################################################
         ## Steps for {mod}
         ####################################################################################
-        .PHONY: sim-rtl{suffix} syn{suffix} syn-to-sim{suffix} sim-syn{suffix} syn-to-par{suffix} par{suffix} par-to-sim{suffix} sim-par{suffix} sim-par-to-power{suffix} par-to-power{suffix} power-par{suffix} par-to-drc{suffix} drc{suffix} par-to-lvs{suffix} lvs{suffix}
+        .PHONY: sim-rtl{suffix} syn{suffix} syn-to-sim{suffix} sim-syn{suffix} syn-to-par{suffix} par{suffix} par-to-sim{suffix} sim-par{suffix} sim-par-to-power{suffix} par-to-power{suffix} power-par{suffix} par-to-drc{suffix} drc{suffix} par-to-lvs{suffix} lvs{suffix} syn-to-formal{suffix} formal-syn{suffix} par-to-formal{suffix} formal-par{suffix}
         sim-rtl{suffix}          : {sim_rtl_out}
         syn{suffix}              : {syn_out}
 
@@ -152,6 +154,12 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
         par-to-lvs{suffix}       : {lvs_in}
         lvs{suffix}              : {lvs_out}
 
+        syn-to-formal{suffix}    : {formal_syn_in}
+        formal-syn{suffix}       : {formal_syn_out}
+
+        par-to-formal{suffix}    : {formal_par_in}
+        formal-par{suffix}       : {formal_par_out}
+
         {par_to_syn}
 
         {sim_rtl_out}: {syn_deps} $(HAMMER_SIM_RTL_DEPENDENCIES)
@@ -199,11 +207,23 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
         {lvs_out}: {lvs_in} $(HAMMER_LVS_DEPENDENCIES)
         \t$(HAMMER_EXEC) {env_confs} -p {lvs_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} lvs{suffix}
 
+        {formal_syn_in}: {syn_out}
+        \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {formal_syn_in} --obj_dir {obj_dir} syn-to-formal
+
+        {formal_syn_out}: {formal_syn_in} $(HAMMER_FORMAL_SYN_DEPENDENCIES)
+        \t$(HAMMER_EXEC) {env_confs} -p {formal_syn_in} $(HAMMER_EXTRA_ARGS) --formal_rundir {formal_syn_run_dir} --obj_dir {obj_dir} formal{suffix}
+
+        {formal_par_in}: {par_out}
+        \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {formal_par_in} --obj_dir {obj_dir} par-to-formal
+
+        {formal_par_out}: {formal_syn_in} $(HAMMER_FORMAL_PAR_DEPENDENCIES)
+        \t$(HAMMER_EXEC) {env_confs} -p {formal_par_in} $(HAMMER_EXTRA_ARGS) --formal_rundir {formal_par_run_dir} --obj_dir {obj_dir} formal{suffix}
+
         # Redo steps
         # These intentionally break the dependency graph, but allow the flexibility to rerun a step after changing a config.
         # Hammer doesn't know what settings impact synthesis only, e.g., so these are for power-users who "know better."
         # The HAMMER_EXTRA_ARGS variable allows patching in of new configurations with -p or using --to_step or --from_step, for example.
-        .PHONY: redo-sim-rtl{suffix} redo-syn{suffix} redo-syn-to-sim{suffix} redo-sim-syn{suffix} redo-syn-to-par{suffix} redo-par{suffix} redo-par-to-sim{suffix} redo-sim-par{suffix} redo-sim-par-to-power{suffix} redo-par-to-power{suffix} redo-power-par{suffix} redo-par-to-drc{suffix} redo-drc{suffix} redo-par-to-lvs{suffix} redo-lvs{suffix}
+        .PHONY: redo-sim-rtl{suffix} redo-syn{suffix} redo-syn-to-sim{suffix} redo-sim-syn{suffix} redo-syn-to-par{suffix} redo-par{suffix} redo-par-to-sim{suffix} redo-sim-par{suffix} redo-sim-par-to-power{suffix} redo-par-to-power{suffix} redo-power-par{suffix} redo-par-to-drc{suffix} redo-drc{suffix} redo-par-to-lvs{suffix} redo-lvs{suffix} redo-syn-to-formal{suffix} redo-formal-syn{suffix} redo-par-to-formal{suffix} redo-formal-par{suffix}
 
         redo-sim-rtl{suffix}:
         \t$(HAMMER_EXEC) {env_confs} {p_sim_rtl_in} $(HAMMER_EXTRA_ARGS) --sim_rundir {sim_rtl_run_dir} --obj_dir {obj_dir} sim{suffix}
@@ -250,6 +270,18 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
         redo-lvs{suffix}:
         \t$(HAMMER_EXEC) {env_confs} -p {lvs_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} lvs{suffix}
 
+        redo-syn-to-formal{suffix}:
+        \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {formal_syn_in} --obj_dir {obj_dir} syn-to-formal
+
+        redo-formal-syn{suffix}:
+        \t$(HAMMER_EXEC) {env_confs} -p {formal_syn_in} $(HAMMER_EXTRA_ARGS) --formal_rundir {formal_syn_run_dir} --obj_dir {obj_dir} formal{suffix}
+
+        redo-par-to-formal{suffix}:
+        \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {formal_par_in} --obj_dir {obj_dir} par-to-formal
+
+        redo-formal-par{suffix}:
+        \t$(HAMMER_EXEC) {env_confs} -p {formal_par_in} $(HAMMER_EXTRA_ARGS) --formal_rundir {formal_par_run_dir} --obj_dir {obj_dir} formal{suffix}
+
         """)
 
     if not dependency_graph:
@@ -265,6 +297,8 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
         power_par_run_dir = os.path.join(obj_dir, "power-par-rundir")
         drc_run_dir = os.path.join(obj_dir, "drc-rundir")
         lvs_run_dir = os.path.join(obj_dir, "lvs-rundir")
+        formal_syn_run_dir = os.path.join(obj_dir, "formal-syn-rundir")
+        formal_par_run_dir = os.path.join(obj_dir, "formal-par-rundir")
 
         p_sim_rtl_in = proj_confs
         sim_rtl_out = os.path.join(sim_rtl_run_dir, "sim-output-full.json")
@@ -283,6 +317,10 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
         drc_out = os.path.join(drc_run_dir, "drc-output-full.json")
         lvs_in = os.path.join(obj_dir, "lvs-input.json")
         lvs_out = os.path.join(lvs_run_dir, "lvs-output-full.json")
+        formal_syn_in = os.path.join(obj_dir, "formal-syn-input.json")
+        formal_syn_out = os.path.join(formal_syn_run_dir, "formal-output-full.json")
+        formal_par_in = os.path.join(obj_dir, "formal-par-input.json")
+        formal_par_out = os.path.join(formal_par_run_dir, "formal-output-full.json")
 
         par_to_syn = ""
 
@@ -293,7 +331,9 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
             sim_par_in=sim_par_in, sim_par_out=sim_par_out, sim_par_run_dir=sim_par_run_dir,
             p_syn_in=p_syn_in, syn_out=syn_out, par_in=par_in, par_out=par_out,
             power_sim_par_in=power_sim_par_in, power_par_in=power_par_in, power_par_out=power_par_out, power_par_run_dir=power_par_run_dir,
-            drc_in=drc_in, drc_out=drc_out, lvs_in=lvs_in, lvs_out=lvs_out)
+            drc_in=drc_in, drc_out=drc_out, lvs_in=lvs_in, lvs_out=lvs_out,
+            formal_syn_in=formal_syn_in, formal_syn_out=formal_syn_out, formal_syn_run_dir=formal_syn_run_dir,
+            formal_par_in=formal_par_in, formal_par_out=formal_par_out, formal_par_run_dir=formal_par_run_dir)
     else:
         # Hierarchical flow
         for node, edges in dependency_graph.items():
@@ -308,6 +348,8 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
             power_par_run_dir = os.path.join(obj_dir, "power-par-" + node)
             drc_run_dir = os.path.join(obj_dir, "drc-" + node)
             lvs_run_dir = os.path.join(obj_dir, "lvs-" + node)
+            formal_syn_run_dir = os.path.join(obj_dir, "formal-syn-" + node)
+            formal_par_run_dir = os.path.join(obj_dir, "formal-par-" + node)
 
             p_sim_rtl_in = proj_confs
             sim_rtl_out = os.path.join(sim_rtl_run_dir, "sim-output-full.json")
@@ -326,6 +368,10 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
             drc_out = os.path.join(drc_run_dir, "drc-output-full.json")
             lvs_in = os.path.join(obj_dir, "lvs-{}-input.json".format(node))
             lvs_out = os.path.join(lvs_run_dir, "lvs-output-full.json")
+            formal_syn_in = os.path.join(obj_dir, "formal-syn-{}-input.json".format(node))
+            formal_syn_out = os.path.join(formal_syn_run_dir, "formal-output-full.json")
+            formal_par_in = os.path.join(obj_dir, "formal-par-{}-input.json".format(node))
+            formal_par_out = os.path.join(formal_par_run_dir, "formal-output-full.json")
 
             # need to revert this each time
             syn_deps = "$(HAMMER_DEPENDENCIES)"
@@ -349,7 +395,9 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None
                 sim_par_in=sim_par_in, sim_par_out=sim_par_out, sim_par_run_dir=sim_par_run_dir,
                 p_syn_in=p_syn_in, syn_out=syn_out, par_in=par_in, par_out=par_out,
                 power_sim_par_in=power_sim_par_in, power_par_in=power_par_in, power_par_out=power_par_out, power_par_run_dir=power_par_run_dir,
-                drc_in=drc_in, drc_out=drc_out, lvs_in=lvs_in, lvs_out=lvs_out)
+                drc_in=drc_in, drc_out=drc_out, lvs_in=lvs_in, lvs_out=lvs_out,
+                formal_syn_in=formal_syn_in, formal_syn_out=formal_syn_out, formal_syn_run_dir=formal_syn_run_dir,
+                formal_par_in=formal_par_in, formal_par_out=formal_par_out, formal_par_run_dir=formal_par_run_dir)
 
     with open(makefile, "w") as f:
         f.write(output)
diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_tool.py b/src/hammer-vlsi/hammer_vlsi/hammer_tool.py
index a3bcbc67b..613662248 100644
--- a/src/hammer-vlsi/hammer_vlsi/hammer_tool.py
+++ b/src/hammer-vlsi/hammer_vlsi/hammer_tool.py
@@ -170,7 +170,7 @@ def run_persistent_step(self, pst: HammerToolHookAction, target_step: HammerTool
         if pst.location == HookLocation.PersistentPostStep:
             self.logger.debug("Running persistent sub-step '{pstep}' after '{step}' (post-step: '{post_step}'".format(pstep=pst.step.name, step=target_step.name, post_step=pst.target_name))
         pst_out = pst.step.func(self)
-        assert isinstance(pst_out, bool)
+        assert pst_out, "Persistent step {step} failed!".format(step=pst.step.name)
         return pst_out
 
     def do_pre_steps(self, first_step: HammerToolStep) -> bool:
@@ -182,13 +182,13 @@ def do_pre_steps(self, first_step: HammerToolStep) -> bool:
         :param first_step: First step to be taken.
         :return: True if successful, False otherwise.
         """
-        pst_out = False
+        pst_out = True
         # Run persistent hooks first
         for pst in list(filter(lambda p: p.location == HookLocation.PersistentStep, self.persistent_steps)):
-            pst_out = self.run_persistent_step(pst, first_step)
+            pst_out = pst_out and self.run_persistent_step(pst, first_step)
         # If pre-persistent hooks target the first step, run them first
         for pst in list(filter(lambda p: p.location == HookLocation.PersistentPreStep and p.target_name == self.first_step.name, self.persistent_steps)):
-            pst_out = self.run_persistent_step(pst, first_step)
+            pst_out = pst_out and self.run_persistent_step(pst, first_step)
         return pst_out
 
     def do_between_steps(self, prev: HammerToolStep, next: HammerToolStep) -> bool:
diff --git a/src/hammer-vlsi/hammer_vlsi/hammer_vlsi_impl.py b/src/hammer-vlsi/hammer_vlsi/hammer_vlsi_impl.py
index 81774af87..4f6b1525e 100644
--- a/src/hammer-vlsi/hammer_vlsi/hammer_vlsi_impl.py
+++ b/src/hammer-vlsi/hammer_vlsi/hammer_vlsi_impl.py
@@ -378,7 +378,6 @@ def sdf_file(self, value: str) -> None:
         self.attr_setter("_sdf_file", value)
 
     ### END Generated interface HammerSynthesisTool ###
-    ### Generated interface HammerSynthesisTool ###
 
 
 class HammerPlaceAndRouteTool(HammerTool):
@@ -396,6 +395,7 @@ def export_config_outputs(self) -> Dict[str, Any]:
         outputs["par.outputs.hcells_list"] = list(self.hcells_list)
         outputs["par.outputs.seq_cells"] = self.output_seq_cells
         outputs["par.outputs.all_regs"] = self.output_all_regs
+        outputs["par.inputs.input_files"] = self.input_files
         outputs["par.inputs.top_module"] = self.top_module
         return outputs
 
@@ -1303,7 +1303,6 @@ def output_saifs(self, value: List[str]) -> None:
         self.attr_setter("_output_saifs", value)
 
     ### END Generated interface HammerSimTool ###
-    ### Generated interface HammerSimTool ###
 
 class HammerPowerTool(HammerTool):
 
@@ -1393,7 +1392,115 @@ def saifs(self, value: List[str]) -> None:
 
     ### Outputs ###
     ### END Generated interface HammerPowerTool ###
-    ### Generated interface HammerPowerTool ###
+
+class HammerFormalTool(HammerTool):
+
+    ### Generated interface HammerFormalTool ###
+    ### DO NOT MODIFY THIS CODE, EDIT generate_properties.py INSTEAD ###
+    ### Inputs ###
+
+    @property
+    def check(self) -> str:
+        """
+        Get the formal verification check type to run.
+
+        :return: The formal verification check type to run.
+        """
+        try:
+            return self.attr_getter("_check", None)
+        except AttributeError:
+            raise ValueError("Nothing set for the formal verification check type to run yet")
+
+    @check.setter
+    def check(self, value: str) -> None:
+        """Set the formal verification check type to run."""
+        if not (isinstance(value, str)):
+            raise TypeError("check must be a str")
+        self.attr_setter("_check", value)
+
+
+    @property
+    def input_files(self) -> List[str]:
+        """
+        Get the input collection of implementation design files.
+
+        :return: The input collection of implementation design files.
+        """
+        try:
+            return self.attr_getter("_input_files", None)
+        except AttributeError:
+            raise ValueError("Nothing set for the input collection of implementation design files yet")
+
+    @input_files.setter
+    def input_files(self, value: List[str]) -> None:
+        """Set the input collection of implementation design files."""
+        if not (isinstance(value, List)):
+            raise TypeError("input_files must be a List[str]")
+        self.attr_setter("_input_files", value)
+
+
+    @property
+    def reference_files(self) -> List[str]:
+        """
+        Get the input collection of reference design files.
+
+        :return: The input collection of reference design files.
+        """
+        try:
+            return self.attr_getter("_reference_files", None)
+        except AttributeError:
+            raise ValueError("Nothing set for the input collection of reference design files yet")
+
+    @reference_files.setter
+    def reference_files(self, value: List[str]) -> None:
+        """Set the input collection of reference design files."""
+        if not (isinstance(value, List)):
+            raise TypeError("reference_files must be a List[str]")
+        self.attr_setter("_reference_files", value)
+
+
+    @property
+    def top_module(self) -> str:
+        """
+        Get the top RTL module.
+
+        :return: The top RTL module.
+        """
+        try:
+            return self.attr_getter("_top_module", None)
+        except AttributeError:
+            raise ValueError("Nothing set for the top RTL module yet")
+
+    @top_module.setter
+    def top_module(self, value: str) -> None:
+        """Set the top RTL module."""
+        if not (isinstance(value, str)):
+            raise TypeError("top_module must be a str")
+        self.attr_setter("_top_module", value)
+
+
+    @property
+    def post_synth_sdc(self) -> Optional[str]:
+        """
+        Get the (optional) input post-synthesis SDC constraint file.
+
+        :return: The (optional) input post-synthesis SDC constraint file.
+        """
+        try:
+            return self.attr_getter("_post_synth_sdc", None)
+        except AttributeError:
+            return None
+
+    @post_synth_sdc.setter
+    def post_synth_sdc(self, value: Optional[str]) -> None:
+        """Set the (optional) input post-synthesis SDC constraint file."""
+        if not (isinstance(value, str) or (value is None)):
+            raise TypeError("post_synth_sdc must be a Optional[str]")
+        self.attr_setter("_post_synth_sdc", value)
+
+
+    ### Outputs ###
+    ### END Generated interface HammerFormalTool ###
 
 class HasUPFSupport(HammerTool):
     """Mix-in trait with functions useful for tools with UPF style power


Test patch:
diff --git a/src/hammer-vlsi/cli_driver_test.py b/src/hammer-vlsi/cli_driver_test.py
index b8f7c541a..3f4f8f30e 100644
--- a/src/hammer-vlsi/cli_driver_test.py
+++ b/src/hammer-vlsi/cli_driver_test.py
@@ -589,13 +589,13 @@ def test_flat_makefile(self) -> None:
 
         targets = self._read_targets_from_makefile(contents)
 
-        tasks = {"pcb", "sim-rtl", "syn", "sim-syn", "par", "sim-par", "power-par", "drc", "lvs"}
-        bridge_tasks = {"syn-to-sim", "syn-to-par", "par-to-sim", "par-to-lvs", "par-to-drc", "par-to-power", "sim-par-to-power"}
+        tasks = {"pcb", "sim-rtl", "syn", "sim-syn", "par", "sim-par", "power-par", "drc", "lvs", "formal-syn", "formal-par"}
+        bridge_tasks = {"syn-to-sim", "syn-to-par", "par-to-sim", "par-to-lvs", "par-to-drc", "par-to-power", "sim-par-to-power", "syn-to-formal", "par-to-formal"}
         expected_targets = tasks.copy()
         expected_targets.update(bridge_tasks)
         expected_targets.update({"redo-" + x for x in expected_targets if x is not "pcb"})
-        expected_targets.update({os.path.join(tmpdir, x + "-rundir", x + "-output-full.json") for x in tasks if x not in {"sim-rtl", "sim-syn", "sim-par", "power-par"}})
-        expected_targets.update({os.path.join(tmpdir, x + "-rundir", x.split('-')[0] + "-output-full.json") for x in tasks if x in {"sim-rtl", "sim-syn", "sim-par", "power-par"}})
+        expected_targets.update({os.path.join(tmpdir, x + "-rundir", x + "-output-full.json") for x in tasks if x not in {"sim-rtl", "sim-syn", "sim-par", "power-par", "formal-syn", "formal-par"}})
+        expected_targets.update({os.path.join(tmpdir, x + "-rundir", x.split('-')[0] + "-output-full.json") for x in tasks if x in {"sim-rtl", "sim-syn", "sim-par", "power-par", "formal-syn", "formal-par"}})
         expected_targets.update({os.path.join(tmpdir, x + "-input.json") for x in tasks if x not in {"syn", "pcb", "sim-rtl", "power-par"}})
         expected_targets.update({os.path.join(tmpdir, x + "-input.json") for x in {"power-par", "power-sim-par"}})
 
@@ -658,12 +658,12 @@ def test_hier_makefile(self) -> None:
 
         mods = {"TopMod", "SubModA", "SubModB"}
         expected_targets = {"pcb", os.path.join(tmpdir, "pcb-rundir", "pcb-output-full.json")}
-        hier_tasks = {"sim-rtl", "syn", "sim-syn", "par", "sim-par", "power-par", "drc", "lvs"}
+        hier_tasks = {"sim-rtl", "syn", "sim-syn", "par", "sim-par", "power-par", "drc", "lvs", "formal-syn", "formal-par"}
         for task in hier_tasks:
             expected_targets.update({task + "-" + x for x in mods})
             expected_targets.update({"redo-" + task + "-" + x for x in mods})
             expected_targets.update({os.path.join(tmpdir, task + "-" + x, task.split("-")[0] + "-output-full.json") for x in mods})
-        bridge_tasks = {"syn-to-sim", "syn-to-par", "par-to-sim", "par-to-lvs", "par-to-drc", "par-to-power", "sim-par-to-power"}
+        bridge_tasks = {"syn-to-sim", "syn-to-par", "par-to-sim", "par-to-lvs", "par-to-drc", "par-to-power", "sim-par-to-power", "syn-to-formal", "par-to-formal"}
         for task in bridge_tasks:
             expected_targets.update({task + "-" + x for x in mods})
             expected_targets.update({"redo-" + task + "-" + x for x in mods})
@@ -677,6 +677,8 @@ def test_hier_makefile(self) -> None:
         expected_targets.update({os.path.join(tmpdir, "power-sim-par-" + x + "-input.json") for x in mods})
         expected_targets.update({os.path.join(tmpdir, "lvs-" + x + "-input.json") for x in mods})
         expected_targets.update({os.path.join(tmpdir, "drc-" + x + "-input.json") for x in mods})
+        expected_targets.update({os.path.join(tmpdir, "formal-syn-" + x + "-input.json") for x in mods})
+        expected_targets.update({os.path.join(tmpdir, "formal-par-" + x + "-input.json") for x in mods})
 
         self.assertEqual(set(targets.keys()), expected_targets)
 
diff --git a/src/test/mypy.sh b/src/test/mypy.sh
index 24f7923be..899df39d0 100755
--- a/src/test/mypy.sh
+++ b/src/test/mypy.sh
@@ -61,6 +61,9 @@ fi
 if [ -f ../../../hammer-cadence-plugins/power/voltus/__init__.py ]; then
     MYPYPATH=$MYPYPATH:../../../hammer-cadence-plugins/common call_mypy ../../../hammer-cadence-plugins/power/voltus/__init__.py
 fi
+if [ -f ../../../hammer-cadence-plugins/formal/conformal/__init__.py ]; then
+    MYPYPATH=$MYPYPATH:../../../hammer-cadence-plugins/common call_mypy ../../../hammer-cadence-plugins/formal/conformal/__init__.py
+fi
 if [ -f ../../../hammer-mentor-plugins/drc/calibre/__init__.py ]; then
     call_mypy ../../../hammer-mentor-plugins/drc/calibre/__init__.py
 fi