2019-01-16 22:26:54 -07:00
|
|
|
import os
|
2018-12-13 01:57:14 -07:00
|
|
|
import re
|
2019-01-16 22:26:54 -07:00
|
|
|
import shutil
|
|
|
|
import subprocess
|
|
|
|
import textwrap
|
|
|
|
import traceback
|
2018-12-13 01:57:14 -07:00
|
|
|
import unittest
|
|
|
|
|
tests: move out of the main package.
Compared to tests in the repository root, tests in the package have
many downsides:
* Unless explicitly excluded in find_packages(), tests and their
support code effectively become a part of public API.
This, unfortunately, happened with FHDLTestCase, which was never
intended for downstream use.
* Even if explicitly excluded from the setuptools package, using
an editable install, or setting PYTHONPATH still allows accessing
the tests.
* Having a sub-package that is present in the source tree but not
exported (or, worse, exported only sometimes) is confusing.
* The name `nmigen.test` cannot be used for anything else, such as
testing utilities that *are* intended for downstream use.
2020-08-26 18:33:31 -06:00
|
|
|
from nmigen.hdl.ast import *
|
|
|
|
from nmigen.hdl.ir import *
|
|
|
|
from nmigen.back import rtlil
|
|
|
|
from nmigen._toolchain import require_tool
|
2018-12-13 01:57:14 -07:00
|
|
|
|
|
|
|
|
|
|
|
__all__ = ["FHDLTestCase"]
|
|
|
|
|
|
|
|
|
|
|
|
class FHDLTestCase(unittest.TestCase):
|
|
|
|
def assertRepr(self, obj, repr_str):
|
2019-07-30 23:19:24 -06:00
|
|
|
if isinstance(obj, list):
|
2019-10-11 07:28:26 -06:00
|
|
|
obj = Statement.cast(obj)
|
2019-01-13 01:51:49 -07:00
|
|
|
def prepare_repr(repr_str):
|
|
|
|
repr_str = re.sub(r"\s+", " ", repr_str)
|
|
|
|
repr_str = re.sub(r"\( (?=\()", "(", repr_str)
|
|
|
|
repr_str = re.sub(r"\) (?=\))", ")", repr_str)
|
|
|
|
return repr_str.strip()
|
|
|
|
self.assertEqual(prepare_repr(repr(obj)), prepare_repr(repr_str))
|
2018-12-13 04:01:03 -07:00
|
|
|
|
2019-01-16 22:26:54 -07:00
|
|
|
def assertFormal(self, spec, mode="bmc", depth=1):
|
2020-10-22 15:38:44 -06:00
|
|
|
stack = traceback.extract_stack()
|
|
|
|
for frame in reversed(stack):
|
|
|
|
if os.path.dirname(__file__) not in frame.filename:
|
|
|
|
break
|
|
|
|
caller = frame
|
|
|
|
|
2019-01-16 22:26:54 -07:00
|
|
|
spec_root, _ = os.path.splitext(caller.filename)
|
|
|
|
spec_dir = os.path.dirname(spec_root)
|
|
|
|
spec_name = "{}_{}".format(
|
|
|
|
os.path.basename(spec_root).replace("test_", "spec_"),
|
|
|
|
caller.name.replace("test_", "")
|
|
|
|
)
|
|
|
|
|
|
|
|
# The sby -f switch seems not fully functional when sby is reading from stdin.
|
|
|
|
if os.path.exists(os.path.join(spec_dir, spec_name)):
|
|
|
|
shutil.rmtree(os.path.join(spec_dir, spec_name))
|
|
|
|
|
2019-01-18 17:52:56 -07:00
|
|
|
if mode == "hybrid":
|
2020-10-15 11:02:50 -06:00
|
|
|
# A mix of BMC and k-induction, as per personal communication with Claire Wolf.
|
2019-01-18 23:02:04 -07:00
|
|
|
script = "setattr -unset init w:* a:nmigen.sample_reg %d"
|
2019-01-18 17:52:56 -07:00
|
|
|
mode = "bmc"
|
|
|
|
else:
|
|
|
|
script = ""
|
|
|
|
|
2019-01-16 22:26:54 -07:00
|
|
|
config = textwrap.dedent("""\
|
|
|
|
[options]
|
|
|
|
mode {mode}
|
|
|
|
depth {depth}
|
2019-01-18 17:52:56 -07:00
|
|
|
wait on
|
2019-01-16 22:26:54 -07:00
|
|
|
|
|
|
|
[engines]
|
|
|
|
smtbmc
|
|
|
|
|
|
|
|
[script]
|
|
|
|
read_ilang top.il
|
|
|
|
prep
|
2019-01-18 17:52:56 -07:00
|
|
|
{script}
|
2019-01-16 22:26:54 -07:00
|
|
|
|
|
|
|
[file top.il]
|
|
|
|
{rtlil}
|
|
|
|
""").format(
|
|
|
|
mode=mode,
|
|
|
|
depth=depth,
|
2019-01-18 17:52:56 -07:00
|
|
|
script=script,
|
2019-01-25 19:31:12 -07:00
|
|
|
rtlil=rtlil.convert(Fragment.get(spec, platform="formal"))
|
2019-01-16 22:26:54 -07:00
|
|
|
)
|
2020-11-05 18:38:03 -07:00
|
|
|
with subprocess.Popen(
|
|
|
|
[require_tool("sby"), "-f", "-d", spec_name],
|
|
|
|
cwd=spec_dir, env={**os.environ, "PYTHONWARNINGS":"ignore"},
|
|
|
|
universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
|
2019-01-16 22:26:54 -07:00
|
|
|
stdout, stderr = proc.communicate(config)
|
|
|
|
if proc.returncode != 0:
|
|
|
|
self.fail("Formal verification failed:\n" + stdout)
|