more code-comments on BitwiseLut
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 17 Dec 2021 12:24:07 +0000 (12:24 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 17 Dec 2021 12:24:07 +0000 (12:24 +0000)
also minor rewrite/style to avoid yosys creating large copies
of expressions (store in temporary signals).
the temp signals have the advantage of drastically simplifying and
clarifying the yosys graphviz output, making it easier to visually
inspect the correctness of the HDL

src/nmutil/lut.py

index b684c65f2a8245092d13ae433fa49db849bc62da..44f3b1c0b15b323724d5f86dc7b93718494d1702 100644 (file)
@@ -1,6 +1,9 @@
 # SPDX-License-Identifier: LGPL-3-or-later
-"""
-Bitwise logic operators implemented using a look-up table, like LUTs in
+# TODO: Copyright notice (standard style, plenty of examples)
+# Copyright (C) 2021 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
+# TODO: credits to NLnet for funding
+
+"""Bitwise logic operators implemented using a look-up table, like LUTs in
 FPGAs. Inspired by x86's `vpternlog[dq]` instructions.
 
 https://bugs.libre-soc.org/show_bug.cgi?id=745
@@ -10,10 +13,11 @@ https://www.felixcloutier.com/x86/vpternlogd:vpternlogq
 from nmigen.hdl.ast import Array, Cat, Repl, Signal
 from nmigen.hdl.dsl import Module
 from nmigen.hdl.ir import Elaboratable
+from nmigen.cli import rtlil
 
 
 class BitwiseMux(Elaboratable):
-    """ Mux, but treating input/output Signals as bit vectors, rather than
+    """ <- XXX no space here>Mux, but treating input/output Signals as bit vectors, rather than
     integers. This means each bit in the output is independently multiplexed
     based on the corresponding bit in each of the inputs.
     """
@@ -31,7 +35,7 @@ class BitwiseMux(Elaboratable):
 
 
 class BitwiseLut(Elaboratable):
-    """ Bitwise logic operators implemented using a look-up table, like LUTs in
+    """ <- XXX no space here>Bitwise logic operators implemented using a look-up table, like LUTs in
         FPGAs. Inspired by x86's `vpternlog[dq]` instructions.
 
         Each output bit `i` is set to `lut[Cat(inp[i] for inp in self.inputs)]`
@@ -49,24 +53,38 @@ class BitwiseLut(Elaboratable):
 
         def inp(i):
             return Signal(width, name=f"input{i}")
-        self.inputs = tuple(inp(i) for i in range(input_count))
-        """ the inputs """
-        self.output = Signal(width)
-        """ the output """
-        self.lut = Signal(2 ** input_count)
-        """ the look-up table. Is `2 ** input_count` bits wide."""
+        self.inputs = tuple(inp(i) for i in range(input_count)) # inputs
+        self.lut = Signal(2 ** input_count)                     # lookup input
+        self.output = Signal(width)                             # output
 
     def elaborate(self, platform):
         m = Module()
-        lut_array = Array(self.lut)
+        comb = m.d.comb
+        lut_array = Array(self.lut) # create dynamic-indexable LUT array
+        out = []
+
         for bit in range(self.width):
-            index = Cat(inp[bit] for inp in self.inputs)
-            m.d.comb += self.output[bit].eq(lut_array[index])
+            # take the bit'th bit of every input, create a LUT index from it
+            index = Signal(self.input_count, name="index%d" % bit)
+            comb += index.eq(Cat(inp[bit] for inp in self.inputs))
+            # store output bit in a list - Cat() it after (simplifies graphviz)
+            outbit = Signal(name="out%d" % bit)
+            comb += outbit.eq(lut_array[index])
+            out.append(outbit)
+
+        # finally Cat() all the output bits together
+        comb += self.output.eq(Cat(*out))
         return m
 
+    def ports(self):
+        return list(self.inputs) + [self.lut, self.output]
+
 
 class TreeBitwiseLut(Elaboratable):
-    """ Tree-based version of BitwiseLut. See BitwiseLut for API documentation.
+    """ <- XXX no space here>Tree-based version of BitwiseLut. See BitwiseLut for API documentation.
+        (good enough reason to say "see bitwiselut", but mention that
+        the API is identical and explain why the second implementation
+        exists, despite it being identical)
     """
 
     def __init__(self, input_count, width):
@@ -88,6 +106,8 @@ class TreeBitwiseLut(Elaboratable):
         return '0b' + ''.join(reversed(k))
 
     def _build_mux_inputs(self, *sel_values):
+        # XXX yyyeah using PHP-style functions-in-text... blech :)
+        # XXX replace with name = mux_input_%s" % self._make_etcetc
         name = f"mux_input_{self._make_key_str(*sel_values)}"
         self._mux_inputs[sel_values] = Signal(self.width, name=name)
         if len(sel_values) < self.input_count:
@@ -99,6 +119,8 @@ class TreeBitwiseLut(Elaboratable):
         m.d.comb += self.output.eq(self._mux_inputs[()])
         for sel_values, v in self._mux_inputs.items():
             if len(sel_values) < self.input_count:
+                # XXX yyyeah using PHP-style functions-in-text... blech :)
+                # XXX replace with name = mux_input_%s" % self._make_etcetc
                 mux_name = f"mux_{self._make_key_str(*sel_values)}"
                 mux = BitwiseMux(self.width)
                 setattr(m.submodules, mux_name, mux)
@@ -115,3 +137,14 @@ class TreeBitwiseLut(Elaboratable):
                         lut_index |= 2 ** i
                 m.d.comb += v.eq(Repl(self.lut[lut_index], self.width))
         return m
+
+    def ports(self):
+        return [self.input, self.chunk_sizes, self.output]
+
+
+# useful to see what is going on: use yosys "read_ilang test_lut.il; show top"
+if __name__ == '__main__':
+    dut = BitwiseLut(3, 8)
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_lut.il", "w") as f:
+        f.write(vl)