From 2e1694ccd5ff0155dfec0fa512bd5b2d822e1a36 Mon Sep 17 00:00:00 2001
From: Vojta Mrazek <mrazek@fit.vutbr.cz>
Date: Fri, 22 Mar 2024 14:19:23 +0100
Subject: [PATCH] popcount and compare

---
 .../arithmetic_circuits/general_circuit.py    | 18 +++-
 .../multi_bit_circuits/adders/__init__.py     |  2 +-
 .../multi_bit_circuits/others/__init__.py     | 12 +++
 .../multi_bit_circuits/others/bit_reduce.py   | 89 +++++++++++++++++++
 .../multi_bit_circuits/others/compare.py      | 83 +++++++++++++++++
 .../multi_bit_circuits/others/popcount.py     | 89 +++++++++++++++++++
 ariths_gen/wire_components/buses.py           |  9 ++
 tests/test_compare.py                         | 50 +++++++++++
 tests/test_popcnt.py                          | 50 +++++++++++
 tests/test_reduce.py                          | 78 ++++++++++++++++
 10 files changed, 477 insertions(+), 3 deletions(-)
 create mode 100644 ariths_gen/multi_bit_circuits/others/__init__.py
 create mode 100644 ariths_gen/multi_bit_circuits/others/bit_reduce.py
 create mode 100644 ariths_gen/multi_bit_circuits/others/compare.py
 create mode 100644 ariths_gen/multi_bit_circuits/others/popcount.py
 create mode 100644 tests/test_compare.py
 create mode 100644 tests/test_popcnt.py
 create mode 100644 tests/test_reduce.py

diff --git a/ariths_gen/core/arithmetic_circuits/general_circuit.py b/ariths_gen/core/arithmetic_circuits/general_circuit.py
index 42af3f6..c4fb720 100644
--- a/ariths_gen/core/arithmetic_circuits/general_circuit.py
+++ b/ariths_gen/core/arithmetic_circuits/general_circuit.py
@@ -1,3 +1,4 @@
+from typing import Dict
 from ariths_gen.core.logic_gate_circuits.logic_gate_circuit import OneInputLogicGate, TwoInputLogicGate
 
 from ariths_gen.wire_components import (
@@ -52,12 +53,22 @@ class GeneralCircuit():
 
         # super().__init__(prefix, name, out_N, inner_component, inputs=[a, b], signed=signed, **kwargs)
 
+
+    def get_circuit_def(self) -> Dict[str, Wire]:
+        """ returns IDs and wires of the inputs and output"""
+     #.{circuit_block.a.prefix}({self.a.prefix}), .{circuit_block.b.prefix}({self.b.prefix}), .{circuit_block.out.prefix}({self.out.prefix}));\n" 
+        r = {chr(97 + i): self.inputs[i] for i in range(len(self.inputs))}
+        r['out'] = self.get_global_prefix() + "_out"
+        return r
+    
     def add_component(self, component):
         """Adds a component into list of circuit's inner subcomponents.
 
         Args:
             component: Subcomponent to be added into list of components composing described circuit.
         """
+        prefixes = [c.prefix for c in self.components]
+        #assert component.prefix not in prefixes, f"Component with prefix {component.prefix} already exists in the circuit."
         self.components.append(component)
         return component
 
@@ -550,6 +561,8 @@ class GeneralCircuit():
         Returns:
             str: Hierarchical Verilog code of subcomponent arithmetic circuit's wires declaration.
         """
+        return "".join(w.get_wire_declaration_v() for w in self.inputs + [self.out]) + "\n"
+
         return f"  wire [{self.a.N-1}:0] {self.a.prefix};\n" + \
                f"  wire [{self.b.N-1}:0] {self.b.prefix};\n" + \
                f"  wire [{self.out.N-1}:0] {self.out.prefix};\n"
@@ -576,8 +589,9 @@ class GeneralCircuit():
         circuit_type = self.__class__(a=Bus("a"), b=Bus("b")).prefix + str(self.N)
         circuit_block = self.__class__(a=Bus(N=self.N, prefix="a"), b=Bus(
             N=self.N, prefix="b"), name=circuit_type)
-        return self.a.return_bus_wires_values_v_hier() + self.b.return_bus_wires_values_v_hier() + \
-            f"  {circuit_type} {circuit_type}_{self.out.prefix}(.{circuit_block.a.prefix}({self.a.prefix}), .{circuit_block.b.prefix}({self.b.prefix}), .{circuit_block.out.prefix}({self.out.prefix}));\n"
+        return "".join([c.return_bus_wires_values_v_hier() for c in self.inputs]) + \
+            f"  {circuit_type} {circuit_type}_{self.out.prefix}(" + ",".join([f".{a.prefix}({b.prefix})" for a, b in zip(circuit_block.inputs, self.inputs)]) + f", .{circuit_block.out.prefix}({self.out.prefix}));\n"
+        #.{circuit_block.a.prefix}({self.a.prefix}), .{circuit_block.b.prefix}({self.b.prefix}), .{circuit_block.out.prefix}({self.out.prefix}));\n"
 
     def get_function_out_v_hier(self):
         """Generates hierarchical Verilog code assignment of corresponding arithmetic circuit's output bus wires.
diff --git a/ariths_gen/multi_bit_circuits/adders/__init__.py b/ariths_gen/multi_bit_circuits/adders/__init__.py
index f0ba494..0ba1f34 100644
--- a/ariths_gen/multi_bit_circuits/adders/__init__.py
+++ b/ariths_gen/multi_bit_circuits/adders/__init__.py
@@ -67,4 +67,4 @@ from ariths_gen.multi_bit_circuits.adders.conditional_sum_adder import (
 from ariths_gen.multi_bit_circuits.adders.carry_increment_adder import (
     UnsignedCarryIncrementAdder,
     SignedCarryIncrementAdder
-)
+)
\ No newline at end of file
diff --git a/ariths_gen/multi_bit_circuits/others/__init__.py b/ariths_gen/multi_bit_circuits/others/__init__.py
new file mode 100644
index 0000000..44ed65b
--- /dev/null
+++ b/ariths_gen/multi_bit_circuits/others/__init__.py
@@ -0,0 +1,12 @@
+
+from ariths_gen.multi_bit_circuits.others.popcount import (
+    UnsignedPopCount
+)
+
+from ariths_gen.multi_bit_circuits.others.bit_reduce import (
+    BitReduce, AndReduce, OrReduce
+)
+
+from ariths_gen.multi_bit_circuits.others.compare import (
+    UnsignedCompareLT
+)
\ No newline at end of file
diff --git a/ariths_gen/multi_bit_circuits/others/bit_reduce.py b/ariths_gen/multi_bit_circuits/others/bit_reduce.py
new file mode 100644
index 0000000..45da4d7
--- /dev/null
+++ b/ariths_gen/multi_bit_circuits/others/bit_reduce.py
@@ -0,0 +1,89 @@
+"""
+
+"""
+
+from ariths_gen.wire_components import (
+    Wire,
+    ConstantWireValue0,
+    ConstantWireValue1,
+    Bus,
+    wires
+)
+from ariths_gen.core.arithmetic_circuits import (
+    ArithmeticCircuit,
+    GeneralCircuit,
+    MultiplierCircuit
+)
+from ariths_gen.core.logic_gate_circuits import (
+    MultipleInputLogicGate
+)
+from ariths_gen.one_bit_circuits.one_bit_components import (
+    HalfAdder,
+    FullAdder,
+    FullAdderP,
+    TwoOneMultiplexer
+)
+from ariths_gen.one_bit_circuits.logic_gates import (
+    AndGate,
+    NandGate,
+    OrGate,
+    NorGate,
+    XorGate,
+    XnorGate,
+    NotGate
+)
+
+from ariths_gen.core.logic_gate_circuits import TwoInputLogicGate, TwoInputInvertedLogicGate, OneInputLogicGate
+from ariths_gen.multi_bit_circuits.adders import UnsignedRippleCarryAdder
+
+from math import log2, ceil
+
+class BitReduce(GeneralCircuit):
+    """Class representing tree reducer circuit. Doent work for NAND gate!
+    """
+
+    def __init__(self, a: Bus, gate : TwoInputLogicGate, prefix : str = "", name : str = "bitreduce", **kwargs):
+        self.N = a.N
+        self.a = a
+
+        outc = 1
+        super().__init__(name=name, prefix=prefix, inputs = [self.a], out_N=outc)
+
+        # tree reduction
+        def create_tree(a: Bus, depth: int, branch="A"):
+
+            #print(a)
+            if a.N == 1:
+                return a[0]
+            else:
+                half = a.N // 2
+                b_in = Bus(N=half, prefix=f"b_inn{depth}A")
+                c_in = Bus(N=a.N - half, prefix=f"b_inn{depth}B")
+                #print(a, half, a.N)
+
+
+                for i, j in enumerate(range(half)):
+                    b_in[i] = a[j]
+                
+                for i, j in enumerate(range(half, a.N)):
+                    c_in[i] = a[j]
+
+                b = create_tree(b_in, depth=depth + 1, branch = "A")
+                c = create_tree(c_in, depth= depth + 1, branch = "B")
+                d = gate(a=b, b=c, prefix = f"{self.prefix}_red_{branch}_{depth}")
+                self.add_component(d)
+                return d.out
+            
+        sumwire = create_tree(self.a, 0, "X")
+        #print(sumbus)
+        self.out[0] = sumwire
+
+
+class OrReduce(BitReduce):
+    def __init__(self, a: Bus, prefix : str = "", name : str = "orreduce", **kwargs):
+        super().__init__(a=a, gate=OrGate, prefix=prefix, name=name, **kwargs)
+
+
+class AndReduce(BitReduce):
+    def __init__(self, a: Bus, prefix : str = "", name : str = "orreduce", **kwargs):
+        super().__init__(a=a, gate=AndGate, prefix=prefix, name=name, **kwargs)
\ No newline at end of file
diff --git a/ariths_gen/multi_bit_circuits/others/compare.py b/ariths_gen/multi_bit_circuits/others/compare.py
new file mode 100644
index 0000000..6c62970
--- /dev/null
+++ b/ariths_gen/multi_bit_circuits/others/compare.py
@@ -0,0 +1,83 @@
+"""
+
+"""
+
+from ariths_gen.wire_components import (
+    Wire,
+    ConstantWireValue0,
+    ConstantWireValue1,
+    Bus,
+    wires
+)
+from ariths_gen.core.arithmetic_circuits import (
+    ArithmeticCircuit,
+    GeneralCircuit,
+    MultiplierCircuit
+)
+
+from ariths_gen.core.logic_gate_circuits import (
+    MultipleInputLogicGate
+)
+from ariths_gen.one_bit_circuits.one_bit_components import (
+    HalfAdder,
+    FullAdder,
+    FullAdderP,
+    TwoOneMultiplexer
+)
+from ariths_gen.one_bit_circuits.logic_gates import (
+    AndGate,
+    NandGate,
+    OrGate,
+    NorGate,
+    XorGate,
+    XnorGate,
+    NotGate
+)
+
+from ariths_gen.multi_bit_circuits.others import OrReduce
+
+
+from math import log2, ceil
+
+class UnsignedCompareLT(GeneralCircuit):
+    """Class representing unsigned compare
+
+    
+    Returns true if a < b
+
+    """
+
+    def __init__(self, a: Bus, b: Bus, prefix : str = "", name : str = "cmp_lt", **kwargs):
+        self.a = a
+        self.b = b
+        self.N = max(a.N, b.N)
+
+        #print("outc", outc)
+        super().__init__(name=name, prefix=prefix, 
+                         inputs = [self.a, self.b], out_N=1)
+
+
+        self.a.bus_extend(self.N, prefix=a.prefix)
+        self.b.bus_extend(self.N, prefix=b.prefix)
+        
+
+        # create wires
+        psum = ConstantWireValue1()
+
+        res = Bus(N = self.N, prefix=self.prefix + "res")
+        
+
+        for i in reversed(range(self.N)):
+
+            i1 = self.add_component(NotGate(self.a[i], f"{self.prefix}_i1_{i}")).out
+            i2 = self.b[i]
+
+            and1 = self.add_component(AndGate(i1, i2, f"{self.prefix}_and1_{i}")).out
+            res[i] = self.add_component(AndGate(and1, psum, f"{self.prefix}_and2_{i}")).out
+
+            pi = self.add_component(XnorGate(self.a[i], self.b[i], f"{self.prefix}_pi_{i}")).out
+            psum = self.add_component(AndGate(pi, psum, f"{self.prefix}_psum_{i}")).out
+
+
+        self.out = self.add_component(OrReduce(res, prefix=f"{self.prefix}_orred")).out
+        #self.out.connect_bus(sumbus  )
\ No newline at end of file
diff --git a/ariths_gen/multi_bit_circuits/others/popcount.py b/ariths_gen/multi_bit_circuits/others/popcount.py
new file mode 100644
index 0000000..cdca327
--- /dev/null
+++ b/ariths_gen/multi_bit_circuits/others/popcount.py
@@ -0,0 +1,89 @@
+"""
+
+"""
+
+from ariths_gen.wire_components import (
+    Wire,
+    ConstantWireValue0,
+    ConstantWireValue1,
+    Bus,
+    wires
+)
+from ariths_gen.core.arithmetic_circuits import (
+    ArithmeticCircuit,
+    GeneralCircuit,
+    MultiplierCircuit
+)
+from ariths_gen.core.logic_gate_circuits import (
+    MultipleInputLogicGate
+)
+from ariths_gen.one_bit_circuits.one_bit_components import (
+    HalfAdder,
+    FullAdder,
+    FullAdderP,
+    TwoOneMultiplexer
+)
+from ariths_gen.one_bit_circuits.logic_gates import (
+    AndGate,
+    NandGate,
+    OrGate,
+    NorGate,
+    XorGate,
+    XnorGate,
+    NotGate
+)
+
+from ariths_gen.multi_bit_circuits.adders import UnsignedRippleCarryAdder
+
+from math import log2, ceil
+
+class UnsignedPopCount(GeneralCircuit):
+    """Class representing unsigned popcount circuit.
+
+    Popcount circuit is a circuit that counts the number of 1s in a binary number.
+
+    """
+
+    def __init__(self, a: Bus, adder : ArithmeticCircuit|None = None, prefix : str = "", name : str = "popcnt", **kwargs):
+        self.N = a.N
+        self.a = a
+
+        outc = ceil(log2(self.N + 1))
+        #print("outc", outc)
+        super().__init__(name=name, prefix=prefix, inputs = [self.a], out_N=outc)
+
+
+        self.a.bus_extend(2**(outc - 1), prefix=a.prefix)
+        #print(self.a)
+        self.adder = adder
+        if not self.adder:
+            self.adder = UnsignedRippleCarryAdder
+
+        # tree reduction
+        def create_tree(a: Bus, depth: int, branch="A"):
+
+            #print(a)
+            if a.N == 1:
+                return a
+            else:
+                half = a.N // 2
+                b_in = Bus(N=half, prefix=f"b_inn{depth}A")
+                c_in = Bus(N=a.N - half, prefix=f"b_inn{depth}B")
+                #print(a, half, a.N)
+
+
+                for i, j in enumerate(range(half)):
+                    b_in[i] = a[j]
+                
+                for i, j in enumerate(range(half, a.N)):
+                    c_in[i] = a[j]
+
+                b = create_tree(b_in, depth=depth + 1, branch = "A")
+                c = create_tree(c_in, depth= depth + 1, branch = "B")
+                d = self.adder(a=b, b=c, prefix = f"{self.prefix}_add{branch}_{depth}")
+                self.add_component(d)
+                return d.out
+            
+        sumbus = create_tree(self.a,0, "X")
+        #print(sumbus)
+        self.out.connect_bus(sumbus  )
\ No newline at end of file
diff --git a/ariths_gen/wire_components/buses.py b/ariths_gen/wire_components/buses.py
index f7c91f5..930d834 100644
--- a/ariths_gen/wire_components/buses.py
+++ b/ariths_gen/wire_components/buses.py
@@ -242,6 +242,15 @@ class Bus():
         [unique_out_wires.append(w.prefix) if w.prefix not in unique_out_wires else None for w in self.bus]
         return "".join([f", .{circuit_block.out.get_wire(self.bus.index(o)).prefix}({unique_out_wires.pop(unique_out_wires.index(o.prefix))})" if o.prefix in unique_out_wires else f", .{circuit_block.out.get_wire(self.bus.index(o)).prefix}()" for o in self.bus])
 
+    def get_wire_declaration_v(self):
+        """Declare the wire in Verilog code representation.
+
+        Returns:
+            str: Verilog code for declaration of individual bus wires.
+        """
+        return f"  wire [{self.N-1}:0] {self.prefix};\n"
+
+
     """ BLIF CODE GENERATION """
     def get_wire_declaration_blif(self, array: bool = True):
         """Declare each wire from the bus independently in Blif code representation.
diff --git a/tests/test_compare.py b/tests/test_compare.py
new file mode 100644
index 0000000..02e1312
--- /dev/null
+++ b/tests/test_compare.py
@@ -0,0 +1,50 @@
+from ariths_gen.wire_components import (
+    Wire,
+    ConstantWireValue0,
+    ConstantWireValue1,
+    Bus
+)
+
+from ariths_gen.core.arithmetic_circuits import GeneralCircuit
+
+from ariths_gen.multi_bit_circuits.others import (
+    UnsignedCompareLT
+)
+
+import numpy as np
+import math
+from io import StringIO
+
+
+def test_compare():
+    """ Test unsigned comparator """
+    N = 8
+
+    a = Bus(N=N, prefix="a")
+    b = Bus(N=N, prefix="b")
+    av = np.arange(2**N).reshape(1, -1)
+    bv = np.arange(2**N).reshape(-1, 1)
+
+
+    cmp = UnsignedCompareLT(a=a, b=b)
+    o = StringIO()
+    cmp.get_v_code_hier(open("tmp.verilog", "w"))
+    print(o.getvalue())
+
+#    av = 0
+ #   bv = 5
+
+
+    v = cmp(av, bv)
+    print("ret = ", v)
+
+    expected = np.array(av < bv).astype(int)
+
+    print("expected = ", expected)
+        
+    #expected = np.sum(r, axis=1)
+
+    np.testing.assert_array_equal(v, expected)
+
+
+    
\ No newline at end of file
diff --git a/tests/test_popcnt.py b/tests/test_popcnt.py
new file mode 100644
index 0000000..8d66628
--- /dev/null
+++ b/tests/test_popcnt.py
@@ -0,0 +1,50 @@
+from ariths_gen.wire_components import (
+    Wire,
+    ConstantWireValue0,
+    ConstantWireValue1,
+    Bus
+)
+
+from ariths_gen.core.arithmetic_circuits import GeneralCircuit
+
+from ariths_gen.multi_bit_circuits.others import (
+    UnsignedPopCount
+)
+
+import numpy as np
+import math
+from io import StringIO
+
+
+def test_popcount():
+    """ Test unsigned adders """
+    N = 7
+
+    for N in [3, 7, 8, 9, 16]:
+        a = Bus(N=N, prefix="a")
+        av = np.arange(2**N)
+
+
+        popcnt = UnsignedPopCount(a=a)
+        #o = StringIO()
+        #popcnt.get_v_code_hier(o)
+        #print(o.getvalue())
+
+
+        print(popcnt(av))
+
+
+        # conv to binary
+        r = []
+        a_s = av.copy()
+        for i in range(N):
+            r.append(a_s % 2)
+            a_s = a_s // 2
+        r = np.dstack(r).reshape(-1, N)
+        print("r = ", r)
+        expected = np.sum(r, axis=1)
+
+        np.testing.assert_array_equal(popcnt(av), expected)
+
+
+    
\ No newline at end of file
diff --git a/tests/test_reduce.py b/tests/test_reduce.py
new file mode 100644
index 0000000..9ec4b7e
--- /dev/null
+++ b/tests/test_reduce.py
@@ -0,0 +1,78 @@
+from ariths_gen.wire_components import (
+    Wire,
+    ConstantWireValue0,
+    ConstantWireValue1,
+    Bus
+)
+
+from ariths_gen.core.arithmetic_circuits import GeneralCircuit
+
+from ariths_gen.multi_bit_circuits.others import (
+    BitReduce, AndReduce, OrReduce
+)
+
+import numpy as np
+import math
+from io import StringIO
+
+
+def test_orreduce():
+    """ Test unsigned adders """
+    N = 7
+
+    for N in [3, 7, 8, 9, 16]:
+        a = Bus(N=N, prefix="a")
+        av = np.arange(2**N)
+
+
+        reduce = OrReduce(a=a)
+        o = StringIO()
+        reduce.get_v_code_hier(o)
+        print(o.getvalue())
+
+
+        #print(reduce(av))
+
+        # conv to binary
+        r = []
+        a_s = av.copy()
+        for i in range(N):
+            r.append(a_s % 2)
+            a_s = a_s // 2
+        r = np.dstack(r).reshape(-1, N)
+        print("r = ", r)
+        expected = np.bitwise_or.reduce(r, axis=1)
+
+        np.testing.assert_array_equal(reduce(av), expected)
+
+
+def test_andreduce():
+    """ Test unsigned adders """
+    N = 7
+
+    for N in [3, 7, 8, 9, 16]:
+        a = Bus(N=N, prefix="a")
+        av = np.arange(2**N)
+
+
+        reduce = AndReduce(a=a)
+        o = StringIO()
+        reduce.get_v_code_hier(o)
+        print(o.getvalue())
+
+
+        #print(reduce(av))
+
+        # conv to binary
+        r = []
+        a_s = av.copy()
+        for i in range(N):
+            r.append(a_s % 2)
+            a_s = a_s // 2
+        r = np.dstack(r).reshape(-1, N)
+        print("r = ", r)
+        expected = np.bitwise_and.reduce(r, axis=1)
+
+        np.testing.assert_array_equal(reduce(av), expected)
+
+    
\ No newline at end of file