more code-comments on BitwiseLut
[nmutil.git] / 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)