Add rudimentary proof to fpmax
authorMichael Nolan <mtnolan2640@gmail.com>
Tue, 28 Jan 2020 16:43:21 +0000 (11:43 -0500)
committerMichael Nolan <mtnolan2640@gmail.com>
Tue, 28 Jan 2020 19:53:47 +0000 (14:53 -0500)
src/ieee754/fpmax/formal/proof.sby [new file with mode: 0644]
src/ieee754/fpmax/formal/proof_fmax_mod.py [new file with mode: 0644]

diff --git a/src/ieee754/fpmax/formal/proof.sby b/src/ieee754/fpmax/formal/proof.sby
new file mode 100644 (file)
index 0000000..6c26b0f
--- /dev/null
@@ -0,0 +1,13 @@
+[options]
+mode bmc
+depth 5
+
+[engines]
+smtbmc yices
+
+[script]
+read_ilang proof.il
+prep -top top
+
+[files]
+proof.il
diff --git a/src/ieee754/fpmax/formal/proof_fmax_mod.py b/src/ieee754/fpmax/formal/proof_fmax_mod.py
new file mode 100644 (file)
index 0000000..386cd0f
--- /dev/null
@@ -0,0 +1,81 @@
+# Proof of correctness for FPMAX module
+# Copyright (C) 2020 Michael Nolan <mtnolan2640@gmail.com>
+
+from nmigen import Module, Signal, Elaboratable
+from nmigen.asserts import Assert, Assume
+from nmigen.cli import rtlil
+
+from ieee754.fpcommon.fpbase import FPNumDecode, FPNumBaseRecord
+from ieee754.fpmax.fpmax import FPMAXPipeMod
+from ieee754.pipeline import PipelineSpec
+import subprocess
+
+
+# This defines a module to drive the device under test and assert
+# properties about its outputs
+class FPMAXDriver(Elaboratable):
+    def __init__(self, pspec):
+        # inputs and outputs
+        self.pspec = pspec
+        self.a = Signal(pspec.width)
+        self.b = Signal(pspec.width)
+        self.z = Signal(pspec.width)
+        self.opc = Signal(pspec.op_wid)
+        self.muxid = Signal(pspec.id_wid)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        m.submodules.dut = dut = FPMAXPipeMod(self.pspec)
+
+        a1 = FPNumBaseRecord(self.pspec.width, False)
+        b1 = FPNumBaseRecord(self.pspec.width, False)
+        z1 = FPNumBaseRecord(self.pspec.width, False)
+        m.submodules.sc_decode_a = a1 = FPNumDecode(None, a1)
+        m.submodules.sc_decode_b = b1 = FPNumDecode(None, b1)
+        m.submodules.sc_decode_z = z1 = FPNumDecode(None, z1)
+
+        m.d.comb += [a1.v.eq(self.a),
+                     b1.v.eq(self.b),
+                     z1.v.eq(self.z)]
+
+        m.d.comb += Assert((z1.v == a1.v) | (z1.v == b1.v) | (z1.v == a1.fp.nan2(0)))
+
+        # connect up the inputs and outputs. I think these could
+        # theoretically be $anyconst/$anysync but I'm not sure nmigen
+        # has support for that
+        m.d.comb += dut.i.a.eq(self.a)
+        m.d.comb += dut.i.b.eq(self.b)
+        m.d.comb += dut.i.ctx.op.eq(self.opc)
+        m.d.comb += dut.i.muxid.eq(self.muxid)
+        m.d.comb += self.z.eq(dut.o.z)
+
+
+        return m
+
+    def ports(self):
+        return [self.a, self.b, self.z, self.opc, self.muxid]
+
+
+def run_test(bits=32):
+    m = FPMAXDriver(PipelineSpec(bits, 2, 1))
+
+    il = rtlil.convert(m, ports=m.ports())
+    with open("proof.il", "w") as f:
+        f.write(il)
+    p = subprocess.Popen(['sby', '-f', 'proof.sby'],
+                         stdout=subprocess.PIPE,
+                         stderr=subprocess.PIPE)
+    if p.wait() == 0:
+        out, _ = p.communicate()
+        print("Proof successful!")
+        print(out.decode('utf-8'))
+    else:
+        print("Proof failed")
+        out, err = p.communicate()
+        print(out.decode('utf-8'))
+        print(err.decode('utf-8'))
+
+
+if __name__ == "__main__":
+    run_test(bits=32)