From 240320200b2ca6cea5fdf50083b09ef2b537f2ab Mon Sep 17 00:00:00 2001 From: Vojta Date: Fri, 13 Dec 2024 13:06:34 +0100 Subject: [PATCH 1/4] pypi version --- README.md | 11 +++++++++++ ariths_gen/__init__.py | 1 + ariths_gen/__version__.py | 1 + 3 files changed, 13 insertions(+) create mode 100644 ariths_gen/__version__.py diff --git a/README.md b/README.md index 8947d28..7061688 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # ArithsGen – tool for arithmetic circuits generation [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) +[![PyPI version fury.io](https://badge.fury.io/py/ariths-gen.svg)](https://pypi.python.org/pypi/ariths-gen/) [![Documentation](https://img.shields.io/badge/api-reference-blue.svg)](https://ehw-fit.github.io/ariths-gen) [![arXiv](https://img.shields.io/badge/arXiv-2203.04649-b31b1b.svg)](https://arxiv.org/abs/2203.04649) @@ -26,6 +27,16 @@ When you use this tool in your work/research, please cite the following article: } ``` +## Instalation +```bash +pip install ariths-gen + +python << EOF +import ariths_gen +print(ariths_gen.__version__) +EOF +``` + ## Prebuild circuits To enable fast work with the circuits, we published pre-build arithmetic circuits in various formats in [generated_circuits](generated_circuits) folder and as a [Release](https://github.com/ehw-fit/ariths-gen/releases). diff --git a/ariths_gen/__init__.py b/ariths_gen/__init__.py index 1434e1d..c7e663c 100644 --- a/ariths_gen/__init__.py +++ b/ariths_gen/__init__.py @@ -16,3 +16,4 @@ from .multi_bit_circuits import ( ) from .pdk import * +from . __version__ import __version__ \ No newline at end of file diff --git a/ariths_gen/__version__.py b/ariths_gen/__version__.py new file mode 100644 index 0000000..5da0b9a --- /dev/null +++ b/ariths_gen/__version__.py @@ -0,0 +1 @@ +__version__ = "1.1.2" \ No newline at end of file From 7db1276d8f2a2768232a4d45bdd4ec6d836f2b27 Mon Sep 17 00:00:00 2001 From: Vojta Date: Fri, 13 Dec 2024 13:10:10 +0100 Subject: [PATCH 2/4] setup cfg --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 17e7560..d98981a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = ariths_gen description = Arithmetic circuits generator to Verilog, BLIF, C etc. -version = attr: ariths_gen.VERSION +version = attr: ariths_gen.__version__ long_description = file: README.md long_description_content_type = text/markdown license = MIT From bc4e191e8ad2a6401a1b675777a01203468f621e Mon Sep 17 00:00:00 2001 From: Vojta Date: Thu, 30 Jan 2025 14:01:58 +0100 Subject: [PATCH 3/4] workflow python version --- .github/workflows/generate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index d1e708f..2c89449 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -123,7 +123,7 @@ jobs: needs: build strategy: matrix: - python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ] + python-version: [ '3.9', '3.10', '3.11', '3.12' ] name: Python ${{ matrix.python-version }} test steps: - uses: actions/checkout@v4 From 07781771064fa1dffde8c46f4db2bce7cef1aa95 Mon Sep 17 00:00:00 2001 From: Vojta Date: Thu, 30 Jan 2025 14:17:08 +0100 Subject: [PATCH 4/4] tools folder; shuffle circuit --- README.md | 2 +- ariths_gen/__init__.py | 4 +- ariths_gen/{ => tools}/pdk.py | 2 +- ariths_gen/tools/shuffle_circuit.py | 209 ++++++++++++++++++++++++++++ generate_axmuls.py | 2 +- tests/test_shuffle.py | 73 ++++++++++ 6 files changed, 288 insertions(+), 4 deletions(-) rename ariths_gen/{ => tools}/pdk.py (96%) create mode 100644 ariths_gen/tools/shuffle_circuit.py create mode 100644 tests/test_shuffle.py diff --git a/README.md b/README.md index 7061688..019bbac 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ https://ehw-fit.github.io/ariths-gen/ . When one uses a specific process design kit (PDK), it is not effective to implement half- and full-adders using two-inputs logic gates. These circuits are directly implemented as CMOS modules and are more effective than heuristic optimization by synthesis tool. If you want to use for example FreePDK45 library, you can call a following function before verilog code generating. ```py -from ariths_gen import set_pdk45_library +from ariths_gen.tools.pdk import set_pdk45_library set_pdk45_library() ``` diff --git a/ariths_gen/__init__.py b/ariths_gen/__init__.py index c7e663c..1b29963 100644 --- a/ariths_gen/__init__.py +++ b/ariths_gen/__init__.py @@ -15,5 +15,7 @@ from .multi_bit_circuits import ( dividers ) -from .pdk import * +from .tools import ( + pdk, shuffle_circuit +) from . __version__ import __version__ \ No newline at end of file diff --git a/ariths_gen/pdk.py b/ariths_gen/tools/pdk.py similarity index 96% rename from ariths_gen/pdk.py rename to ariths_gen/tools/pdk.py index 41d3a12..12b709d 100644 --- a/ariths_gen/pdk.py +++ b/ariths_gen/tools/pdk.py @@ -10,7 +10,7 @@ You may add your own modules as defined in the example below NOTE: Please call this function before calling `get_v_code_XXX()` to allow the Verilog generation process to take into account the library's specific definitions. """ -from .one_bit_circuits import ( +from ..one_bit_circuits import ( one_bit_components ) diff --git a/ariths_gen/tools/shuffle_circuit.py b/ariths_gen/tools/shuffle_circuit.py new file mode 100644 index 0000000..8ea196e --- /dev/null +++ b/ariths_gen/tools/shuffle_circuit.py @@ -0,0 +1,209 @@ + + + + +from io import StringIO +from ..wire_components import ( + + ConstantWireValue0, + ConstantWireValue1, + Bus +) +from ..core.arithmetic_circuits import GeneralCircuit +from ..core.cgp_circuit import UnsignedCGPCircuit + +from ..one_bit_circuits.logic_gates import ( + AndGate, + NandGate, + OrGate, + NorGate, + XorGate, + XnorGate, + NotGate +) +import re +import random + +class ShuffleCircuit(UnsignedCGPCircuit): + + @staticmethod + def from_circuit(circuit: GeneralCircuit, strategy = "random"): + o = StringIO() + circuit.get_cgp_code_flat(o) + + cgp = o.getvalue() + return ShuffleCircuit(code=cgp.strip(), strategy=strategy, input_widths=[i.N for i in circuit.inputs]) + + + def __init__(self, code: str = "", strategy = "random", input_widths: list = None, inputs: list = None, prefix: str = "", name: str = "cgp", **kwargs): + """ + Initializes the CGP (Cartesian Genetic Programming) circuit by parsing the provided code and setting up the inputs, + internal nodes, and outputs. The circuit is shuffled and maintained as an acyclic forward graph. + Args: + code (str): The CGP code string defining the circuit structure. + input_widths (list): List of widths for each input bus. + strategy: random, min or max + inputs (list): List of input Bus objects. + prefix (str): Prefix for naming components. + name (str): Name of the circuit. + **kwargs: Additional keyword arguments for the GeneralCircuit initialization. + Raises: + AssertionError: If neither inputs nor input_widths are provided, or if both are provided. + ValueError: If there are backward or loop connections in the CGP genes, or if output connections are out of range. + """ + + cgp_prefix, cgp_core, cgp_outputs = re.match( + r"{(.*)}(.*)\(([^()]+)\)", code).groups() + + c_in, c_out, c_rows, c_cols, c_ni, c_no, c_lback = map( + int, cgp_prefix.split(",")) + + assert inputs is not None or input_widths is not None, "Either inputs or input_widths must be provided" + + if inputs: + assert input_widths is None, "Only one of inputs or input_widths must be provided" + + input_widths =[i.N for i in inputs] + assert sum(input_widths) == c_in, f"CGP input width {c_in} doesn't match inputs {inputs_widths}" + + + + else: + + assert sum( + input_widths) == c_in, f"CGP input width {c_in} doesn't match input_widths {input_widths}" + + def get_name(i): + if i < 26: + return chr(i + 0x61) + assert i < 26 * 26 + return chr(i // 26 + 0x60) + chr(i % 26 + 0x61) + + inputs = [Bus(N=bw, prefix=f"input_{get_name(i)}") + for i, bw in enumerate(input_widths)] + + # Assign each Bus object in self.inputs to a named attribute of self + for bus in inputs: + # Here, bus.prefix is 'input_a', 'input_b', etc. + # We strip 'input_' and use the remaining part (e.g., 'a', 'b') to create the attribute name + attr_name = bus.prefix.replace('input_', '') + setattr(self, attr_name, bus) + + # Adding values to the list + self.vals = {} + j = 2 # Start from two, 0=False, 1=True + for iid, bw in enumerate(input_widths): + for i in range(bw): + assert j not in self.vals + self.vals[j] = inputs[iid].get_wire(i) + j += 1 + + GeneralCircuit.__init__(self, prefix=prefix, name=name, out_N=c_out, inputs=inputs, **kwargs) + + if not code: + return # only for getting the name + + cgp_core = cgp_core.split(")(") + + + + i = 0 + + values = [] + for definition in cgp_core: + i, in_a, in_b, fn = map(int, re.match( + r"\(?\[(\d+)\](\d+),(\d+),(\d+)\)?", definition).groups()) + + if in_a > i or in_b > i: + raise ValueError(f"Backward connection in CGP gene \"{definition}\", maxid = {i}") + + if in_a == i or in_b == i: + raise ValueError(f"Loop connection in CGP gene: \"{definition}\", maxid = {i}") + + values.append((i, in_a, in_b, fn)) + + connected = [] + disconnected = values.copy() + + to_connect = [] + + while disconnected or to_connect: + + # get the values that can be connected + disconnected2 = [] + for i, in_a, in_b, fn in disconnected: + # pick the nodes that can be connected + if fn in [8, 9] or (in_a in self.vals and fn in [0, 1]) or (in_a in self.vals and in_b in self.vals): + to_connect.append((i, in_a, in_b, fn)) + else: + disconnected2.append((i, in_a, in_b, fn)) + + disconnected = disconnected2 + + # print("To connect: ", to_connect) + # print("Disconnected: ", disconnected) + # print("Vals", self.vals.keys()) + # print("") + + + if strategy == "random": + # randomly pick one value from to_connect + picked = random.choice(to_connect) + elif strategy == "max": + # select the value with the highest i + picked = max(to_connect, key=lambda x: x[0]) + elif strategy == "min": + # select the value with the lowest i + picked = min(to_connect, key=lambda x: x[0]) + else: + raise ValueError(f"Unknown strategy {strategy}, can be random, min or max") + + i, in_a, in_b, fn = picked + + to_connect.remove(picked) + + # remove the picked value from disconnected + comp_set = dict(prefix=f"{self.prefix}_core_{i:03d}", parent_component=self) + + a = b = None + + if fn not in [8, 9]: + a = self._get_wire(in_a) + + if fn not in [0, 1]: + b = self._get_wire(in_b) + + if fn == 0: # IDENTITY + o = a + elif fn == 1: # NOT + o = self.add_component(NotGate(a, **comp_set)).out + elif fn == 2: # AND + o = self.add_component(AndGate(a, b, **comp_set)).out + elif fn == 3: # OR + o = self.add_component(OrGate(a, b, **comp_set)).out + elif fn == 4: # XOR + o = self.add_component(XorGate(a, b, **comp_set)).out + elif fn == 5: # NAND + o = self.add_component(NandGate(a, b, **comp_set)).out + elif fn == 6: # NOR + o = self.add_component(NorGate(a, b, **comp_set)).out + elif fn == 7: # XNOR + o = self.add_component(XnorGate(a, b, **comp_set)).out + elif fn == 8: # TRUE + o = ConstantWireValue1() + elif fn == 9: # FALSE + o = ConstantWireValue0() + + assert i not in self.vals + self.vals[i] = o + + # print("Disconnected: ", disconnected) + # print("vals: ", self.vals.keys()) + # Output connection + for i, o in enumerate(map(int, cgp_outputs.split(","))): + if o >= c_in + c_rows * c_cols + 2: + raise ValueError( + f"Output {i} is connected to wire {o} which is not in the range of CGP wires ({c_in + c_rows * c_cols + 2})") + w = self._get_wire(o) + self.out.connect(i, w) + diff --git a/generate_axmuls.py b/generate_axmuls.py index 6346bc2..12f938e 100644 --- a/generate_axmuls.py +++ b/generate_axmuls.py @@ -20,7 +20,7 @@ from ariths_gen.multi_bit_circuits.approximate_multipliers import ( UnsignedBrokenCarrySaveMultiplier ) -from ariths_gen.pdk import * +from ariths_gen.tools.pdk import set_pdk45_library import os from itertools import product diff --git a/tests/test_shuffle.py b/tests/test_shuffle.py new file mode 100644 index 0000000..210a462 --- /dev/null +++ b/tests/test_shuffle.py @@ -0,0 +1,73 @@ + +from ariths_gen.multi_bit_circuits.multipliers import UnsignedArrayMultiplier +import numpy as np + +from ariths_gen.tools.shuffle_circuit import ShuffleCircuit +from ariths_gen.wire_components import Bus +from io import StringIO + + +def test_shuffle_circuit(): + a = Bus(N=4, prefix="a") + b = Bus(N=4, prefix="b") + m = UnsignedArrayMultiplier(a, b, prefix="m") + + na = np.arange(0, 2**4).reshape(-1, 1) + nb = np.arange(0, 2**4).reshape(1, -1) + + assert(np.all(m(na, nb) == na * nb)) + + o = StringIO() + m.get_cgp_code_flat(o) + + shuffled = ShuffleCircuit.from_circuit(m, strategy="random") + assert(np.all(shuffled(na, nb) == na * nb)) + + +def test_shuffle_cgp(): + a = Bus(N=4, prefix="a") + b = Bus(N=4, prefix="b") + m = UnsignedArrayMultiplier(a, b, prefix="m") + + na = np.arange(0, 2**4).reshape(-1, 1) + nb = np.arange(0, 2**4).reshape(1, -1) + + assert(np.all(m(na, nb) == na * nb)) + + o = StringIO() + m.get_cgp_code_flat(o) + + cgp = o.getvalue() + + shuffled = ShuffleCircuit(code=cgp.strip(), input_widths=[4, 4]) + assert(np.all(shuffled(na, nb) == na * nb)) + +def test_shuffle_strategies(): + a = Bus(N=4, prefix="a") + b = Bus(N=4, prefix="b") + m = UnsignedArrayMultiplier(a, b, prefix="m") + + na = np.arange(0, 2**4).reshape(-1, 1) + nb = np.arange(0, 2**4).reshape(1, -1) + + assert(np.all(m(na, nb) == na * nb)) + + o = StringIO() + m.get_cgp_code_flat(o) + + cgp = o.getvalue() + + shuffled = ShuffleCircuit(code=cgp.strip(), input_widths=[4, 4], strategy="min") + assert(np.all(shuffled(na, nb) == na * nb)) + + shuffled = ShuffleCircuit(code=cgp.strip(), input_widths=[4, 4], strategy="max") + assert(np.all(shuffled(na, nb) == na * nb)) + + shuffled = ShuffleCircuit(code=cgp.strip(), input_widths=[4, 4], strategy="random") + assert(np.all(shuffled(na, nb) == na * nb)) + + +if __name__ == "__main__": + test_shuffle_cgp() + test_shuffle_circuit() + test_shuffle_strategies() \ No newline at end of file