Fill the second nibble of the pattern
[ieee754fpu.git] / src / ieee754 / part / formal / proof_partition.py
1 """Formal verification of partitioned operations
2
3 The approach is to take an arbitrary partition, by choosing its start point
4 and size at random. Use ``Assume`` to ensure it is a whole unbroken partition
5 (start and end points are one, with only zeros in between). Shift inputs and
6 outputs down to zero. Loop over all possible partition sizes and, if it's the
7 right size, compute the expected value, compare with the result, and assert.
8
9 We are turning the for-loops around (on their head), such that we start from
10 the *lengths* (and positions) and perform the ``Assume`` on the resultant
11 partition bits.
12
13 In other words, we have patterns as follows (assuming 32-bit words)::
14
15 8-bit offsets 0,1,2,3
16 16-bit offsets 0,1,2
17 24-bit offsets 0,1
18 32-bit
19
20 * for 8-bit the partition bit is 1 and the previous is also 1
21
22 * for 16-bit the partition bit at the offset must be 0 and be surrounded by 1
23
24 * for 24-bit the partition bits at the offset and at offset+1 must be 0 and at
25 offset+2 and offset-1 must be 1
26
27 * for 32-bit all 3 bits must be 0 and be surrounded by 1 (guard bits are added
28 at each end for this purpose)
29
30 """
31
32 import os
33 import unittest
34
35 from nmigen import Elaboratable, Signal, Module, Const
36 from nmigen.asserts import Assert, Cover
37
38 from nmutil.formaltest import FHDLTestCase
39 from nmutil.gtkw import write_gtkw
40
41 from ieee754.part_mul_add.partpoints import PartitionPoints
42
43
44 class PartitionedPattern(Elaboratable):
45 """ Generate a unique pattern, depending on partition size.
46
47 * 1-byte partitions: 0x11
48 * 2-byte partitions: 0x21 0x22
49 * 3-byte partitions: 0x31 0x32 0x33
50
51 And so on.
52
53 Useful as a test vector for testing the formal prover
54
55 """
56 def __init__(self, width, partition_points):
57 self.width = width
58 self.partition_points = PartitionPoints(partition_points)
59 self.mwidth = len(self.partition_points)+1
60 self.output = Signal(self.width, reset_less=True)
61
62 def elaborate(self, platform):
63 m = Module()
64 comb = m.d.comb
65
66 # Add a guard bit at each end
67 positions = [0] + list(self.partition_points.keys()) + [self.width]
68 gates = [Const(1)] + list(self.partition_points.values()) + [Const(1)]
69 # Begin counting at one
70 last_start = positions[0]
71 last_end = positions[1]
72 comb += self.output[last_start:last_end].eq(1)
73 # Build an incrementing cascade
74 for i in range(1, self.mwidth):
75 start = positions[i]
76 end = positions[i+1]
77 # Propagate from the previous byte, adding one to it.
78 with m.If(~gates[i]):
79 comb += self.output[start:end].eq(
80 self.output[last_start:last_end] + 1)
81 with m.Else():
82 # ... unless it's a partition boundary. If so, start again.
83 comb += self.output[start:end].eq(1)
84 last_start = start
85 last_end = end
86 # Mirror the nibbles on the last byte
87 last_start = positions[-2]
88 last_end = positions[-1]
89 last_middle = (last_start+last_end)//2
90 comb += self.output[last_middle:last_end].eq(
91 self.output[last_start:last_middle])
92 for i in range(self.mwidth, 0, -1):
93 start = positions[i-1]
94 end = positions[i]
95 middle = (start + end) // 2
96 # Propagate from the previous byte.
97 with m.If(~gates[i]):
98 comb += self.output[middle:end].eq(
99 self.output[last_middle:last_end])
100 with m.Else():
101 # ... unless it's a partition boundary.
102 # If so, mirror the nibbles again.
103 comb += self.output[middle:end].eq(
104 self.output[start:middle])
105 last_start = start
106 last_middle = middle
107 last_end = end
108
109 return m
110
111
112 # This defines a module to drive the device under test and assert
113 # properties about its outputs
114 class Driver(Elaboratable):
115 def __init__(self):
116 # inputs and outputs
117 pass
118
119 @staticmethod
120 def elaborate(_):
121 m = Module()
122 comb = m.d.comb
123 width = 64
124 mwidth = 8
125 # Setup partition points and gates
126 points = PartitionPoints()
127 gates = Signal(mwidth-1)
128 step = int(width/mwidth)
129 for i in range(mwidth-1):
130 points[(i+1)*step] = gates[i]
131 # Instantiate the partitioned pattern producer
132 m.submodules.dut = dut = PartitionedPattern(width, points)
133 # Directly check some cases
134 with m.If(gates == 0):
135 comb += Assert(dut.output == 0x_88_87_86_85_84_83_82_81)
136 with m.If(gates == 0b1100101):
137 comb += Assert(dut.output == 0x_11_11_33_32_31_22_21_11)
138 with m.If(gates == 0b0001000):
139 comb += Assert(dut.output == 0x_44_43_42_41_44_43_42_41)
140 with m.If(gates == 0b0100001):
141 comb += Assert(dut.output == 0x_22_21_55_54_53_52_51_11)
142 with m.If(gates == 0b1000001):
143 comb += Assert(dut.output == 0x_11_66_65_64_63_62_61_11)
144 with m.If(gates == 0b0000001):
145 comb += Assert(dut.output == 0x_77_76_75_74_73_72_71_11)
146 # Make it interesting, by having three partitions
147 comb += Cover(sum(gates) == 3)
148 return m
149
150
151 class PartitionTestCase(FHDLTestCase):
152 def test_formal(self):
153 traces = ['output[63:0]', 'gates[6:0]']
154 write_gtkw(
155 'test_formal_cover.gtkw',
156 os.path.dirname(__file__) +
157 '/proof_partition_formal/engine_0/trace0.vcd',
158 traces,
159 module='top.dut',
160 zoom="formal"
161 )
162 write_gtkw(
163 'test_formal_bmc.gtkw',
164 os.path.dirname(__file__) +
165 '/proof_partition_formal/engine_0/trace.vcd',
166 traces,
167 module='top.dut',
168 zoom="formal"
169 )
170 module = Driver()
171 self.assertFormal(module, mode="bmc", depth=1)
172 self.assertFormal(module, mode="cover", depth=1)
173
174
175 if __name__ == '__main__':
176 unittest.main()