Merge pull request #9 from ehw-fit/devel

Develop
This commit is contained in:
Vojta Mrazek 2022-01-13 16:16:02 +01:00 committed by GitHub
commit d445f9e3c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 959 additions and 41 deletions

View File

@ -28,10 +28,12 @@ jobs:
# You can test your matrix by printing the current Python version
- name: Display Python version
run: python -c "import sys; print(sys.version)"
- name: Run generating
- name: Run generating adds and mults
run: python generate_test.py
- name: Run generating
- name: Run generating mac
run: python generate_mac.py
- name: Run generating axmults
run: python generate_axmuls.py
- name: Upload results
uses: actions/upload-artifact@v1.0.0
with:

119
README.md
View File

@ -1,34 +1,126 @@
# ArithsGen tool for arithmetic circuits generation
### FIT BUT bachelor's degree project
[![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/)
[![Documentation](https://img.shields.io/badge/api-reference-blue.svg)](https://ehw-fit.github.io/ariths-gen)
## Description
ArithsGen presents an open source tool that enables generation of various arithmetic circuits along with the possibility to export them to various representations which all serve their specific purpose. C language for easy simulation, Verilog for logic synthesis, BLIF for formal verification possibilities and CGP to enable further global optimization.
In contrast to standard HDL languages Python supports
* Multiple output formats (BLIF, Verilog, C, Integer netlis)
* Advanced langugage construction (better configuration, inheritance, etc.)
* Support of various PDKs (for using library cells as half-adders and full-adders)
## Prebuild circuits
To enable the 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).
### Usage
```bash
python3 ariths_gen.py
python3 generate_test.py
cd test_circuits
ls
```
### Example of generation
#Example generation of Verilog representation of 8-bit unsigned dadda multiplier that uses cla to provide the final product
a = Bus(N=8, prefix="a_bus")
b = Bus(N=8, prefix="b_bus")
```py
#Example generation of Verilog representation of 8-bit unsigned dadda multiplier that uses cla to provide the final product
a = Bus(N=8, prefix="a_bus")
b = Bus(N=8, prefix="b_bus")
u_dadda = UnsignedDaddaMultiplier(a=a, b=b, prefix="h_u_dadda_cla8", unsigned_adder_class_name=UnsignedCarryLookaheadAdder)
u_dadda.get_v_code_hier(open("h_u_dadda_cla8.v", "w"))
u_dadda = UnsignedDaddaMultiplier(a=a, b=b, prefix="h_u_dadda_cla8", unsigned_adder_class_name=UnsignedCarryLookaheadAdder)
u_dadda.get_v_code_hier(open("h_u_dadda_cla8.v", "w"))
```
### Simple arithmetic circuits
See [Ripple Carry Adder](ariths_gen/multi_bit_circuits/adders/ripple_carry_adder.py) file for a basic example.
### Complex circuits
It is possible to combine some basic circuit to generate more complex circuits (such as MAC). The design can be parametrised (i.e., you can pass `UnsignedArraymultiplier` as an input parameter).
```py
from ariths_gen.core.arithmetic_circuits.arithmetic_circuit import ArithmeticCircuit
from ariths_gen.core.arithmetic_circuits import GeneralCircuit
from ariths_gen.wire_components import Bus, Wire
from ariths_gen.multi_bit_circuits.adders import UnsignedRippleCarryAdder
from ariths_gen.multi_bit_circuits.multipliers import UnsignedArrayMultiplier, UnsignedDaddaMultiplier
import os
class MAC(GeneralCircuit):
def __init__(self, a: Bus, b: Bus, r: Bus, prefix: str = "", name: str = "mac", **kwargs):
super().__init__(prefix=prefix, name=name, out_N=2*a.N+1, inputs=[a, b, r], **kwargs)
assert a.N == b.N
assert r.N == 2 * a.N
self.mul = self.add_component(UnsignedArrayMultiplier(a=a, b=b, prefix=self.prefix, name=f"u_arrmul{a.N}", inner_component=True))
self.add = self.add_component(UnsignedRippleCarryAdder(a=r, b=self.mul.out, prefix=self.prefix, name=f"u_rca{r.N}", inner_component=True))
self.out.connect_bus(connecting_bus=self.add.out)
# usage
if __name__ == "__main__":
os.makedirs("test_circuits/mac", exist_ok=True)
mymac = MAC(Bus("a", 8), Bus("b", 8), Bus("acc", 16))
mymac.get_v_code_hier(open("test_circuits/mac/mac_hier.v", "w"))
mymac.get_c_code_hier(open("test_circuits/mac/mac_hier.c", "w"))
mymac.get_c_code_flat(open("test_circuits/mac/mac_flat.c", "w"))
```
## Documentation
Code documentation is provided using **pdoc** documentation generator tool. Source: https://pdoc3.github.io/pdoc/.
The automatically generated documentation is available at
https://ehw-fit.github.io/ariths-gen/ .
### Installation
```bash
pip3 install pdoc3
## Supporting various PDK kits
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
set_pdk45_library()
```
### Usage
You can add a support of arbitrary PDK (see an [example](ariths_gen/pdk.py) ).
## Approximate circuits
Besides the accurate arithmetic circuits you can generate some approximate circuits. Currently we support _Broken Array Multiplier_ and _Truncated Multiplier_. For more details please follow files in folder [approximate_multipliers](ariths_gen/multi_bit_circuits/approximate_multipliers/). You can simply run
```bash
pdoc --html ariths_gen
python3 generate_axmuls.py
```
to get the approximate circuits.
The module supports also evaluation of the proposed circuits. You can call the instation as a function (even with numpy-array input) to obtain the results for one multiplication
```py
from ariths_gen.wire_components.buses import Bus
from ariths_gen.multi_bit_circuits.approximate_multipliers import UnsignedBrokenArrayMultiplier
a = Bus(N=8, prefix="a_bus")
b = Bus(N=8, prefix="b_bus")
# Create BAM
bam = UnsignedBrokenArrayMultiplier(a, b, horizontal_cut=4, vertical_cut=4)
print("43 * 84 = ", bam(43, 84), " expected: ", 43 * 84)
# 43 * 84 = 3440 expected: 3612
```
even for all possible combinations
```py
# Evaluate all using b'casting
import numpy as np
import matplotlib.pyplot as plt
va = np.arange(256).reshape(1, -1)
vb = va.reshape(-1, 1)
r = bam(va, vb)
cax = plt.imshow(np.abs(r - (va * vb)))
plt.colorbar(cax)
plt.title("Absolute difference")
plt.xlabel("a")
plt.ylabel("b")
print("Mean average error", np.abs(r - (va * vb)).mean())
# Mean average error 956.25
```
![bam (v=4,h=4)](bam.png)
## Formal verification
The `yosys_equiv_check.sh` script enables to formally check the equivalence of generated Verilog and BLIF representations of the same circuit.
@ -51,7 +143,6 @@ For more detailed description of script's usage, use help.
## CGP testing
The `chr2c.py` script converts the input CGP chromosome generated by ArithsGen to the corresponding C code and prints it to standard output.
It is a modified version of a script originally authored by Vojtech Mrazek.
### Usage
```bash

View File

@ -11,5 +11,8 @@ from .one_bit_circuits import (
from .multi_bit_circuits import (
adders,
multipliers,
approximate_multipliers,
dividers
)
from .pdk import *

View File

@ -127,16 +127,20 @@ class GeneralCircuit():
return multi_bit_comps
@staticmethod
def get_unique_types(components: list):
def get_unique_types(components: list, multi_bit: bool = False):
"""Retrieves just the unique representatives of class types present inside the provided components list.
Args:
components (list): List of components to be filtered.
multi_bit (bool): Specifies whether the provided components list is composed of multi bit type circuits. Defaults to False.
Returns:
list: List of unique composite class types.
"""
return list({type(c): c for c in components}.values())
if multi_bit is True:
return list({(type(c), c.N): c for c in components}.values())
else:
return list({type(c): c for c in components}.values())
def get_component_types(self):
"""Retrieves a list of all the unique types of subcomponents composing the circuit.
@ -150,7 +154,7 @@ class GeneralCircuit():
one_bit_comps = self.get_unique_types(
components=self.get_one_bit_components())
multi_bit_comps = self.get_unique_types(
components=self.get_multi_bit_components())
components=self.get_multi_bit_components(), multi_bit=True)
all_components = gate_comps + one_bit_comps + multi_bit_comps
return all_components
@ -407,7 +411,7 @@ class GeneralCircuit():
str: Hierarchical C code of subcomponent's C function invocation and output assignment.
"""
# Getting name of circuit type for proper C code generation without affecting actual generated composition
circuit_type = self.prefix.replace(circuit_prefix+"_", "")
circuit_type = self.__class__(a=Bus("a"), b=Bus("b")).prefix + str(self.N)
return self.a.return_bus_wires_values_c_hier() + self.b.return_bus_wires_values_c_hier() + \
f" {self.out.prefix} = {circuit_type}({self.a.prefix}, {self.b.prefix});\n"
@ -559,13 +563,9 @@ class GeneralCircuit():
str: Hierarchical Verilog code of subcomponent's module invocation and output assignment.
"""
# Getting name of circuit type and insitu copying out bus for proper Verilog code generation without affecting actual generated composition
circuit_type = self.prefix.replace(circuit_prefix+"_", "")
# Obtain proper circuit name with its bit width
circuit_prefix = self.__class__(
a=Bus("a"), b=Bus("b")).prefix + str(self.N)
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_prefix)
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"
@ -671,7 +671,7 @@ class GeneralCircuit():
str: Hierarchical Blif code of subcomponent's model invocation and output assignment.
"""
# Getting name of circuit type for proper Blif code generation without affecting actual generated composition
circuit_type = self.prefix.replace(circuit_prefix+"_", "")
circuit_type = self.__class__(a=Bus("a"), b=Bus("b")).prefix + str(self.N)
return f"{self.a.get_wire_assign_blif(output=True)}" + \
f"{self.b.get_wire_assign_blif(output=True)}" + \
f".subckt {circuit_type}" + \

View File

@ -29,22 +29,43 @@ class MultiplierCircuit(ArithmeticCircuit):
that are later used for generation into various representations.
"""
def __init__(self, a, b, prefix, name, out_N, **kwargs):
def __init__(self, a, b, prefix: str, name: str, out_N: int, **kwargs):
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=out_N, **kwargs)
# Array multipliers
def get_previous_partial_product(self, a_index: int, b_index: int):
def get_previous_partial_product(self, a_index: int, b_index: int, mult_type=""):
"""Used in array multipliers to get previous row's component output wires for further connection to another component's input.
Args:
a_index (int): First input wire index.
b_index (int): Second input wire index.
mult_type (string, optional): Specifies what type of multiplier circuit has called this method. It is used for proper retrieval of index into the components list to allow appropriate interconnection of the multiplier circuit's inner subcomponents. It expects "" for ordinary multipliers, `bam` or `tm` for specific approximate multipliers. Defaults to "".
Returns:
Wire: Previous row's component wire of corresponding pp.
"""
# To get the index of previous row's connecting adder and its generated pp
index = ((b_index-2) * (self.N*2)) + ((self.N-1)+2*(a_index+2))
if mult_type == "bam":
#TODO alter to be more compact
ids_sum = 0
for row in range(self.horizontal_cut + self.ommited_rows, b_index):
first_row_elem_id = self.vertical_cut-row if self.vertical_cut-row > 0 else 0
# First pp row composed just from gates
if row == self.horizontal_cut + self.ommited_rows:
# Minus one because the first component has index 0 instead of 1
ids_sum += sum([1 for gate_pos in range(first_row_elem_id, self.N)])-1
elif row == b_index-1:
ids_sum += sum([2 for gate_adder_pos in range(first_row_elem_id, self.N) if gate_adder_pos <= a_index+1])
else:
ids_sum += sum([2 for gate_adder_pos in range(first_row_elem_id, self.N)])
# Index calculation should be redone, but it works even this way
index = ids_sum+2 if a_index == self.N-1 else ids_sum
elif mult_type == "tm":
index = ((b_index-self.truncation_cut-2) * ((self.N-self.truncation_cut)*2)) + ((self.N-self.truncation_cut-1)+2*(a_index-self.truncation_cut+2))
else:
index = ((b_index-2) * ((self.N)*2)) + ((self.N-1)+2*(a_index+2))
# Get carry wire as input for the last adder in current row
if a_index == self.N-1:

View File

@ -67,7 +67,7 @@ class UnsignedCarryLookaheadAdder(ArithmeticCircuit):
"""
def __init__(self, a: Bus, b: Bus, cla_block_size: int = 4, prefix: str = "", name: str = "u_cla", **kwargs):
self.N = max(a.N, b.N)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N+1, **kwargs)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N+1, **kwargs)
# Bus sign extension in case buses have different lengths
self.a.bus_extend(N=self.N, prefix=a.prefix)

View File

@ -65,7 +65,7 @@ class UnsignedPGRippleCarryAdder(ArithmeticCircuit):
def __init__(self, a: Bus, b: Bus, prefix: str = "", name: str = "u_pg_rca", **kwargs):
self.N = max(a.N, b.N)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N+1, **kwargs)
# Bus sign extension in case buses have different lengths
self.a.bus_extend(N=self.N, prefix=a.prefix)
self.b.bus_extend(N=self.N, prefix=b.prefix)

View File

@ -0,0 +1,9 @@
from ariths_gen.multi_bit_circuits.approximate_multipliers.truncated_multiplier import (
UnsignedTruncatedMultiplier,
SignedTruncatedMultiplier
)
from ariths_gen.multi_bit_circuits.approximate_multipliers.broken_array_multiplier import (
UnsignedBrokenArrayMultiplier,
SignedBrokenArrayMultiplier
)

View File

@ -0,0 +1,321 @@
from ariths_gen.wire_components import (
Wire,
ConstantWireValue0,
ConstantWireValue1,
Bus
)
from ariths_gen.core.arithmetic_circuits import (
ArithmeticCircuit,
MultiplierCircuit
)
from ariths_gen.one_bit_circuits.one_bit_components import (
HalfAdder,
FullAdder,
FullAdderPG
)
from ariths_gen.one_bit_circuits.logic_gates import (
AndGate,
NandGate,
OrGate,
NorGate,
XorGate,
XnorGate,
NotGate
)
from ariths_gen.multi_bit_circuits.multipliers import(
UnsignedArrayMultiplier,
SignedArrayMultiplier
)
class UnsignedBrokenArrayMultiplier(MultiplierCircuit):
"""Class representing unsigned broken array multiplier.
It represents an approximative version of unsigned array multiplier with simpler structure.
It is created by modifying an ordinary N-bit unsigned array multiplier by omitting partial product
stage cells by the specified horizontal and vertical cut levels.
The design promises better area and power parameters in exchange for the loss of computation precision.
The BAM design allows to save more partial product stage adders than truncated multiplier.
```
VERTICAL CUT=4
A3B0 A2B0 A1B0 A0B0
AND AND AND AND
A3B1 A2B1 A1B1 A0B1
AND AND AND AND
HA FA FA HA
HORIZONTAL CUT=2
A3B2 A2B2 A1B2 A0B2
AND AND AND AND
FA FA HA
A3B3 A2B3 A1B3 A0B3
AND AND AND AND
HA HA FA HA
P7 P6 P5 P4=0 P3=0 P2=0 P1=0 P0=0
```
Description of the __init__ method.
Args:
a (Bus): First input bus.
b (Bus): Second input bus.
horizontal_cut (int, optional): Specifies horizontal cut used in broken array multiplier circuit creation. Defaults to 0.
vertical_cut (int, optional): Specifies vertical cut used in broken array multiplier circuit creation. Defaults to 0.
prefix (str, optional): Prefix name of unsigned broken array multiplier. Defaults to "".
name (str, optional): Name of unsigned broken array multiplier. Defaults to "u_bam".
"""
def __init__(self, a: Bus, b: Bus, horizontal_cut: int = 0, vertical_cut: int = 0, prefix: str = "", name: str = "u_bam", **kwargs):
# NOTE: If horizontal/vertical cut is specified as 0 the final circuit is a simple array multiplier
self.horizontal_cut = horizontal_cut
self.vertical_cut = vertical_cut
self.N = max(a.N, b.N)
# Horizontal cut level should be: 0 <= horizontal_cut < N
# Vertical cut level should be: horizontal_cut <= vertical_cut < 2*N
assert horizontal_cut < self.N
assert vertical_cut < 2*self.N
# Vertical cut should be greater or equal to horizontal cut
assert vertical_cut >= horizontal_cut
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, **kwargs)
# Bus sign extension in case buses have different lengths
self.a.bus_extend(N=self.N, prefix=a.prefix)
self.b.bus_extend(N=self.N, prefix=b.prefix)
self.ommited_rows = 0
# Gradual generation of partial products
for b_multiplier_index in range(self.horizontal_cut, self.N):
# Number of elements that should be ommited in the current level based on vertical cut
pp_row_elems_to_skip = self.vertical_cut - b_multiplier_index if self.vertical_cut - b_multiplier_index > 0 else 0
# Number of pp pairs present in the current row
pp_row_elems = self.N-pp_row_elems_to_skip if self.N-pp_row_elems_to_skip > 0 else 0
self.ommited_rows += 1 if pp_row_elems == 0 else 0
for a_multiplicand_index in range((self.N-pp_row_elems), self.N):
# AND gates generation for calculation of partial products
obj_and = AndGate(self.a.get_wire(a_multiplicand_index), self.b.get_wire(b_multiplier_index), prefix=self.prefix+"_and"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_and)
if b_multiplier_index != self.horizontal_cut + self.ommited_rows:
if b_multiplier_index == self.horizontal_cut + self.ommited_rows + 1:
previous_product = self.components[a_multiplicand_index + b_multiplier_index - self.vertical_cut].out
else:
previous_product = self.get_previous_partial_product(a_index=a_multiplicand_index, b_index=b_multiplier_index, mult_type="bam")
# HA generation for first 1-bit adder in each row starting from the second one
if a_multiplicand_index == 0 or self.vertical_cut-b_multiplier_index == a_multiplicand_index:
obj_adder = HalfAdder(self.get_previous_component().out, previous_product, prefix=self.prefix+"_ha"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# Product generation
self.out.connect(b_multiplier_index, obj_adder.get_sum_wire())
# HA generation, last 1-bit adder in second row
elif a_multiplicand_index == self.N-1 and b_multiplier_index == self.horizontal_cut+self.ommited_rows+1:
obj_adder = HalfAdder(self.get_previous_component().out, self.get_previous_component(number=2).get_carry_wire(), prefix=self.prefix+"_ha"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# FA generation
else:
obj_adder = FullAdder(self.get_previous_component().out, previous_product, self.get_previous_component(number=2).get_carry_wire(), prefix=self.prefix+"_fa"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# PRODUCT GENERATION
if (a_multiplicand_index == 0 and b_multiplier_index == self.horizontal_cut) or (self.horizontal_cut + self.ommited_rows == self.N-1):
self.out.connect(a_multiplicand_index + b_multiplier_index, obj_and.out)
# 1 bit multiplier case
if a_multiplicand_index == self.N-1 and b_multiplier_index == self.N-1:
self.out.connect(a_multiplicand_index+b_multiplier_index+1, ConstantWireValue0())
elif b_multiplier_index == self.N-1 and self.horizontal_cut != self.N-1:
self.out.connect(b_multiplier_index + a_multiplicand_index, obj_adder.get_sum_wire())
if a_multiplicand_index == self.N-1:
self.out.connect(self.out.N-1, obj_adder.get_carry_wire())
# Connecting the output bits generated from ommited cells to ground
if self.vertical_cut == 2*self.N-1:
[self.out.connect(out_id, ConstantWireValue0()) for out_id in range(self.out.N)]
else:
for grounded_out_index in range(0, max(self.horizontal_cut, self.vertical_cut)):
self.out.connect(grounded_out_index, ConstantWireValue0())
class SignedBrokenArrayMultiplier(MultiplierCircuit):
"""Class representing signed broken array multiplier.
It represents an approximative version of signed array multiplier with simpler structure.
It is created by modifying an ordinary N-bit unsigned array multiplier by omitting partial product
stage cells by the specified horizontal and vertical cut levels.
The design promises better area and power parameters in exchange for the loss of computation precision.
The BAM design allows to save more partial product stage adders than truncated multiplier.
TODO
```
A3B0 A2B0 A1B0 A0B0
NAND AND AND AND
A3B1 A2B1 A1B1 A0B1
NAND AND AND AND
1
FA FA FA HA
A3B2 A2B2 A1B2 A0B2
NAND AND AND AND
FA FA FA HA
A3B3 A2B3 A1B3 A0B3
AND NAND NAND NAND
1
XOR FA FA FA HA
P7 P6 P5 P4 P3 P2 P1 P0
```
Description of the __init__ method.
Args:
a (Bus): First input bus.
b (Bus): Second input bus.
horizontal_cut (int, optional): Specifies horizontal cut used in signed broken array multiplier circuit creation. Defaults to 0.
vertical_cut (int, optional): Specifies vertical cut used in signed broken array multiplier circuit creation. Defaults to 0.
prefix (str, optional): Prefix name of signed broken array multiplier. Defaults to "".
name (str, optional): Name of signed broken array multiplier. Defaults to "s_bam".
"""
def __init__(self, a: Bus, b: Bus, horizontal_cut: int = 0, vertical_cut: int = 0, prefix: str = "", name: str = "s_bam", **kwargs):
# NOTE: If horizontal/vertical cut is specified as 0 the final circuit is a simple array multiplier
self.horizontal_cut = horizontal_cut
self.vertical_cut = vertical_cut
self.N = max(a.N, b.N)
# Horizontal cut level should be: 0 <= horizontal_cut < N
# Vertical cut level should be: horizontal_cut <= vertical_cut < 2*N
assert horizontal_cut < self.N
assert vertical_cut < 2*self.N
# Vertical cut should be greater or equal to horizontal cut
assert vertical_cut >= horizontal_cut
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Bus sign extension in case buses have different lengths
self.a.bus_extend(N=self.N, prefix=a.prefix)
self.b.bus_extend(N=self.N, prefix=b.prefix)
self.ommited_rows = 0
# Gradual generation of partial products
for b_multiplier_index in range(self.horizontal_cut, self.N):
# Number of elements that should be ommited in the current level based on vertical cut
pp_row_elems_to_skip = self.vertical_cut - b_multiplier_index if self.vertical_cut - b_multiplier_index > 0 else 0
# Number of pp pairs present in the current row
pp_row_elems = self.N-pp_row_elems_to_skip if self.N-pp_row_elems_to_skip > 0 else 0
self.ommited_rows += 1 if pp_row_elems == 0 else 0
for a_multiplicand_index in range(self.N-pp_row_elems, self.N):
# AND and NAND gates generation for calculation of partial products and sign extension
if (b_multiplier_index == self.N-1 and a_multiplicand_index != self.N-1) or (b_multiplier_index != self.N-1 and a_multiplicand_index == self.N-1):
obj_nand = NandGate(self.a.get_wire(a_multiplicand_index), self.b.get_wire(b_multiplier_index), prefix=self.prefix+"_nand"+str(a_multiplicand_index)+"_"+str(b_multiplier_index), parent_component=self)
self.add_component(obj_nand)
else:
obj_and = AndGate(self.a.get_wire(a_multiplicand_index), self.b.get_wire(b_multiplier_index), prefix=self.prefix+"_and"+str(a_multiplicand_index)+"_"+str(b_multiplier_index), parent_component=self)
self.add_component(obj_and)
if b_multiplier_index != self.horizontal_cut + self.ommited_rows:
if b_multiplier_index == self.horizontal_cut + self.ommited_rows + 1:
previous_product = self.components[a_multiplicand_index + b_multiplier_index - self.vertical_cut].out
else:
previous_product = self.get_previous_partial_product(a_index=a_multiplicand_index, b_index=b_multiplier_index, mult_type="bam")
# HA generation for first 1-bit adder in each row starting from the second one
if a_multiplicand_index == 0 or self.vertical_cut-b_multiplier_index == a_multiplicand_index:
obj_adder = HalfAdder(self.get_previous_component().out, previous_product, prefix=self.prefix+"_ha"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# Product generation
self.out.connect(b_multiplier_index, obj_adder.get_sum_wire())
# FA generation
else:
# Constant wire with value 1 used at the last FA in second row (as one of its inputs) for signed multiplication (based on Baugh Wooley algorithm)
if a_multiplicand_index == self.N-1 and b_multiplier_index == self.horizontal_cut+self.ommited_rows+1:
previous_product = ConstantWireValue1()
obj_adder = FullAdder(self.get_previous_component().out, previous_product, self.get_previous_component(number=2).get_carry_wire(), prefix=self.prefix+"_fa"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# PRODUCT GENERATION
if (a_multiplicand_index == 0 and b_multiplier_index == self.horizontal_cut) or (self.horizontal_cut + self.ommited_rows == self.N-1):
self.out.connect(a_multiplicand_index + b_multiplier_index, obj_and.out)
# 1 bit multiplier case
if a_multiplicand_index == self.N-1 and b_multiplier_index == self.N-1:
obj_nor = NorGate(ConstantWireValue1(), self.get_previous_component().out, prefix=self.prefix+"_nor_zero_extend", parent_component=self)
self.add_component(obj_nor)
self.out.connect(a_multiplicand_index+1, obj_nor.out)
elif b_multiplier_index == self.N-1 and self.horizontal_cut != self.N-1:
self.out.connect(b_multiplier_index + a_multiplicand_index, obj_adder.get_sum_wire())
if a_multiplicand_index == self.N-1:
obj_xor = XorGate(self.get_previous_component().get_carry_wire(), ConstantWireValue1(), prefix=self.prefix+"_xor"+str(a_multiplicand_index+1)+"_"+str(b_multiplier_index), parent_component=self)
self.add_component(obj_xor)
self.out.connect(self.out.N-1, obj_xor.out)
# Connecting the output bits generated from ommited cells to ground
if self.vertical_cut == 2*self.N-1:
[self.out.connect(out_id, ConstantWireValue0()) for out_id in range(self.out.N)]
else:
for grounded_out_index in range(0, max(self.horizontal_cut, self.vertical_cut)):
self.out.connect(grounded_out_index, ConstantWireValue0())

View File

@ -0,0 +1,270 @@
from ariths_gen.wire_components import (
Wire,
ConstantWireValue0,
ConstantWireValue1,
Bus
)
from ariths_gen.core.arithmetic_circuits import (
ArithmeticCircuit,
MultiplierCircuit
)
from ariths_gen.one_bit_circuits.one_bit_components import (
HalfAdder,
FullAdder,
FullAdderPG
)
from ariths_gen.one_bit_circuits.logic_gates import (
AndGate,
NandGate,
OrGate,
NorGate,
XorGate,
XnorGate,
NotGate
)
from ariths_gen.multi_bit_circuits.multipliers import(
UnsignedArrayMultiplier,
SignedArrayMultiplier
)
class UnsignedTruncatedMultiplier(MultiplierCircuit):
"""Class representing unsigned truncated multiplier.
It represents an approximative version of unsigned array multiplier with simpler structure.
It is created by modifying an ordinary N-bit unsigned array multiplier by ignoring
(truncating) some of the partial products.
The design promises better area and power parameters in exchange for the loss of computation precision.
```
CUT=2
A3B0 A2B0 A1B0 A0B0
AND AND AND AND
A3B1 A2B1 A1B1 A0B1
AND AND AND AND
HA FA FA HA
CUT=2
A3B2 A2B2 A1B2 A0B2
AND AND AND AND
FA HA
A3B3 A2B3 A1B3 A0B3
AND AND AND AND
HA HA FA HA
P7 P6 P5 P4 P3=0 P2=0 P1=0 P0=0
```
Description of the __init__ method.
Args:
a (Bus): First input bus.
b (Bus): Second input bus.
truncation_cut (int, optional): Specifies truncation cut level used in the truncated multiplier circuit creation. Note: If equal to 0, the final circuit behaves as an ordinary array multiplier. Defaults to 0.
prefix (str, optional): Prefix name of unsigned truncated multiplier. Defaults to "".
name (str, optional): Name of unsigned truncated multiplier. Defaults to "u_tm".
"""
def __init__(self, a: Bus, b: Bus, truncation_cut: int = 0, prefix: str = "", name: str = "u_tm", **kwargs):
# NOTE: If truncation_cut is specified as 0 the final circuit is a simple array multiplier
self.truncation_cut = truncation_cut
self.N = max(a.N, b.N)
# Cut level should be: 0 <= truncation_cut < N
assert truncation_cut < self.N
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, **kwargs)
# Bus sign extension in case buses have different lengths
self.a.bus_extend(N=self.N, prefix=a.prefix)
self.b.bus_extend(N=self.N, prefix=b.prefix)
# Gradual generation of partial products
for b_multiplier_index in range(self.truncation_cut, self.N):
for a_multiplicand_index in range(self.truncation_cut, self.N):
# AND gates generation for calculation of partial products
obj_and = AndGate(self.a.get_wire(a_multiplicand_index), self.b.get_wire(b_multiplier_index), prefix=self.prefix+"_and"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_and)
if b_multiplier_index != self.truncation_cut:
previous_product = self.components[a_multiplicand_index + b_multiplier_index - 2*self.truncation_cut].out if b_multiplier_index == self.truncation_cut + 1 else self.get_previous_partial_product(a_index=a_multiplicand_index, b_index=b_multiplier_index, mult_type="tm")
# HA generation for first 1-bit adder in each row starting from the second one
if a_multiplicand_index == self.truncation_cut:
obj_adder = HalfAdder(self.get_previous_component().out, previous_product, prefix=self.prefix+"_ha"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# Product generation
self.out.connect(b_multiplier_index + self.truncation_cut, obj_adder.get_sum_wire())
# HA generation, last 1-bit adder in second row
elif a_multiplicand_index == self.N-1 and b_multiplier_index == self.truncation_cut+1:
obj_adder = HalfAdder(self.get_previous_component().out, self.get_previous_component(number=2).get_carry_wire(), prefix=self.prefix+"_ha"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# FA generation
else:
obj_adder = FullAdder(self.get_previous_component().out, previous_product, self.get_previous_component(number=2).get_carry_wire(), prefix=self.prefix+"_fa"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# PRODUCT GENERATION
if (a_multiplicand_index == self.truncation_cut and b_multiplier_index == self.truncation_cut) or (self.truncation_cut == self.N-1):
self.out.connect(a_multiplicand_index + b_multiplier_index, obj_and.out)
# 1 bit multiplier case
if a_multiplicand_index == self.N-1 and b_multiplier_index == self.N-1:
self.out.connect(a_multiplicand_index+b_multiplier_index+1, ConstantWireValue0())
elif b_multiplier_index == self.N-1 and self.truncation_cut != self.N-1:
self.out.connect(b_multiplier_index + a_multiplicand_index, obj_adder.get_sum_wire())
if a_multiplicand_index == self.N-1:
self.out.connect(self.out.N-1, obj_adder.get_carry_wire())
# Connecting the output bits generated from ommited cells to ground
for grounded_out_index in range(0, self.truncation_cut*2):
self.out.connect(grounded_out_index, ConstantWireValue0())
class SignedTruncatedMultiplier(MultiplierCircuit):
"""Class representing signed truncated multiplier.
It represents an approximative version of signed array multiplier with simpler structure.
It is created by modifying an ordinary N-bit signed array multiplier by ignoring
(truncating) some of the partial products.
The design promises better area and power parameters in exchange for the loss of computation precision.
```TODO
A3B0 A2B0 A1B0 A0B0
NAND AND AND AND
A3B1 A2B1 A1B1 A0B1
NAND AND AND AND
1
FA FA FA HA
A3B2 A2B2 A1B2 A0B2
NAND AND AND AND
FA FA FA HA
A3B3 A2B3 A1B3 A0B3
AND NAND NAND NAND
1
XOR FA FA FA HA
P7 P6 P5 P4 P3 P2 P1 P0
```
Description of the __init__ method.
Args:
a (Bus): First input bus.
b (Bus): Second input bus.
truncation_cut (int, optional): Specifies truncation cut level used in the truncated multiplier circuit creation. Note: If equal to 0, the final circuit behaves as an ordinary array multiplier. Defaults to 0.
prefix (str, optional): Prefix name of signed truncated multiplier. Defaults to "".
name (str, optional): Name of signed truncated multiplier. Defaults to "s_tm".
"""
def __init__(self, a: Bus, b: Bus, truncation_cut: int = 0, prefix: str = "", name: str = "s_tm", **kwargs):
# NOTE: If truncation_cut is specified as 0 the final circuit is a simple array multiplier
self.truncation_cut = truncation_cut
self.N = max(a.N, b.N)
# Cut level should be: 0 <= truncation_cut < N
assert truncation_cut < self.N
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Bus sign extension in case buses have different lengths
self.a.bus_extend(N=self.N, prefix=a.prefix)
self.b.bus_extend(N=self.N, prefix=b.prefix)
# Gradual generation of partial products
for b_multiplier_index in range(self.truncation_cut, self.N):
for a_multiplicand_index in range(self.truncation_cut, self.N):
# AND and NAND gates generation for calculation of partial products and sign extension
if (b_multiplier_index == self.N-1 and a_multiplicand_index != self.N-1) or (b_multiplier_index != self.N-1 and a_multiplicand_index == self.N-1):
obj_nand = NandGate(self.a.get_wire(a_multiplicand_index), self.b.get_wire(b_multiplier_index), prefix=self.prefix+"_nand"+str(a_multiplicand_index)+"_"+str(b_multiplier_index), parent_component=self)
self.add_component(obj_nand)
else:
obj_and = AndGate(self.a.get_wire(a_multiplicand_index), self.b.get_wire(b_multiplier_index), prefix=self.prefix+"_and"+str(a_multiplicand_index)+"_"+str(b_multiplier_index), parent_component=self)
self.add_component(obj_and)
if b_multiplier_index != self.truncation_cut:
previous_product = self.components[a_multiplicand_index + b_multiplier_index - 2*self.truncation_cut].out if b_multiplier_index == self.truncation_cut + 1 else self.get_previous_partial_product(a_index=a_multiplicand_index, b_index=b_multiplier_index, mult_type="tm")
# HA generation for first 1-bit adder in each row starting from the second one
if a_multiplicand_index == self.truncation_cut:
obj_adder = HalfAdder(self.get_previous_component().out, previous_product, prefix=self.prefix+"_ha"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# Product generation
self.out.connect(b_multiplier_index + self.truncation_cut, obj_adder.get_sum_wire())
# FA generation
else:
# Constant wire with value 1 used at the last FA in second row (as one of its inputs) for signed multiplication (based on Baugh Wooley algorithm)
if a_multiplicand_index == self.N-1 and b_multiplier_index == self.truncation_cut+1:
previous_product = ConstantWireValue1()
obj_adder = FullAdder(self.get_previous_component().out, previous_product, self.get_previous_component(number=2).get_carry_wire(), prefix=self.prefix+"_fa"+str(a_multiplicand_index)+"_"+str(b_multiplier_index))
self.add_component(obj_adder)
# PRODUCT GENERATION
if (a_multiplicand_index == self.truncation_cut and b_multiplier_index == self.truncation_cut) or (self.truncation_cut == self.N-1):
self.out.connect(a_multiplicand_index + b_multiplier_index, obj_and.out)
# 1 bit multiplier case
if a_multiplicand_index == self.N-1 and b_multiplier_index == self.N-1:
obj_nor = NorGate(ConstantWireValue1(), self.get_previous_component().out, prefix=self.prefix+"_nor_zero_extend", parent_component=self)
self.add_component(obj_nor)
self.out.connect(a_multiplicand_index+1, obj_nor.out)
elif b_multiplier_index == self.N-1 and self.truncation_cut != self.N-1:
self.out.connect(b_multiplier_index + a_multiplicand_index, obj_adder.get_sum_wire())
if a_multiplicand_index == self.N-1:
obj_xor = XorGate(self.get_previous_component().get_carry_wire(), ConstantWireValue1(), prefix=self.prefix+"_xor"+str(a_multiplicand_index+1)+"_"+str(b_multiplier_index), parent_component=self)
self.add_component(obj_xor)
self.out.connect(self.out.N-1, obj_xor.out)
# Connecting the output bits generated from ommited cells to ground
for grounded_out_index in range(0, self.truncation_cut*2):
self.out.connect(grounded_out_index, ConstantWireValue0())

View File

@ -193,7 +193,7 @@ class SignedArrayMultiplier(MultiplierCircuit):
self.N = max(a.N, b.N)
super().__init__(a=a, b=b, prefix=prefix, name=name, out_N=self.N*2, signed=True, **kwargs)
self.c_data_type = "int64_t"
# Bus sign extension in case buses have different lengths
self.a.bus_extend(N=self.N, prefix=a.prefix)
self.b.bus_extend(N=self.N, prefix=b.prefix)

View File

@ -23,6 +23,8 @@ class FullAdder(ThreeInputOneBitCircuit):
c (Wire, optional): Carry input wire. Defaults to Wire(name="cin").
prefix (str, optional): Prefix name of full adder. Defaults to "fa".
"""
use_verilog_instance = False
def __init__(self, a: Wire = Wire(name="a"), b: Wire = Wire(name="b"), c: Wire = Wire(name="cin"), prefix: str = "fa"):
super().__init__(a, b, c, prefix)
# 2 wires for component's bus output (sum, cout)
@ -50,6 +52,40 @@ class FullAdder(ThreeInputOneBitCircuit):
self.out.connect(1, obj_or.out)
def get_init_v_flat(self):
""" support of custom PDK """
if not self.use_verilog_instance:
return super().get_init_v_flat()
return " " + self.use_verilog_instance.format(
**{
"unit": self.prefix,
"wirea": self.a.prefix,
"wireb": self.b.prefix,
"wirec": self.c.prefix,
"wireys": self.get_sum_wire().prefix,
"wireyc": self.get_carry_wire().prefix,
}
) + ";\n"
def get_self_init_v_hier(self):
""" support of custom PDK """
if not self.use_verilog_instance:
return super().get_self_init_v_hier()
unique_out_wires = []
for o in self.out.bus:
unique_out_wires.append(o.name+"_outid"+str(self.out.bus.index(o))) if o.is_const() or o.name in [self.a.name, self.b.name] else unique_out_wires.append(o.name)
return " " + self.use_verilog_instance.format(**{
"unit": self.prefix,
"wirea": self.a.name,
"wireb": self.b.name,
"wirec": self.c.name,
"wireys": unique_out_wires[0],
"wireyc": unique_out_wires[1],
}) + ";\n"
class FullAdderPG(ThreeInputOneBitCircuit):
"""Class representing modified three input one bit full adder with propagate/generate logic.

View File

@ -21,6 +21,8 @@ class HalfAdder(TwoInputOneBitCircuit):
b (Wire, optional): Second input wire. Defaults to Wire(name="b").
prefix (str, optional): Prefix name of half adder. Defaults to "ha".
"""
use_verilog_instance = False
def __init__(self, a: Wire = Wire(name="a"), b: Wire = Wire(name="b"), prefix: str = "ha"):
super().__init__(a, b, prefix)
# 2 wires for component's bus output (sum, cout)
@ -38,6 +40,38 @@ class HalfAdder(TwoInputOneBitCircuit):
self.add_component(obj_and)
self.out.connect(1, obj_and.out)
def get_init_v_flat(self):
""" support of custom PDK """
if not self.use_verilog_instance:
return super().get_init_v_flat()
return " " + self.use_verilog_instance.format(
**{
"unit": self.prefix,
"wirea": self.a.prefix,
"wireb": self.b.prefix,
"wireys": self.get_sum_wire().prefix,
"wireyc": self.get_carry_wire().prefix,
}
) + ";\n"
def get_self_init_v_hier(self):
""" support of custom PDK """
if not self.use_verilog_instance:
return super().get_self_init_v_hier()
unique_out_wires = []
for o in self.out.bus:
unique_out_wires.append(o.name+"_outid"+str(self.out.bus.index(o))) if o.is_const() or o.name in [self.a.name, self.b.name] else unique_out_wires.append(o.name)
return " " + self.use_verilog_instance.format(**{
"unit": self.prefix,
"wirea": self.a.name,
"wireb": self.b.name,
"wireys": unique_out_wires[0],
"wireyc": unique_out_wires[1],
}) + ";\n"
class PGLogicBlock(TwoInputOneBitCircuit):
"""Class representing two input one bit propagate/generate logic block.

18
ariths_gen/pdk.py Normal file
View File

@ -0,0 +1,18 @@
"""
Support of custom PDK
This file defines functions for generating of full and half adders
directly on the level of CMOS modules.
You may add your own modules as in example in set_pdk45_library()
Please call this function before calling get_verilog_code_XXX()
"""
from .one_bit_circuits import (
one_bit_components
)
def set_pdk45_library():
one_bit_components.FullAdder.use_verilog_instance = "FAX1 {unit} (.A({wirea}), .B({wireb}), .C({wirec}), .YS({wireys}), .YC({wireyc}))"
one_bit_components.HalfAdder.use_verilog_instance = "HAX1 {unit} (.A({wirea}), .B({wireb}), .YS({wireys}), .YC({wireyc}))"

View File

@ -9,7 +9,7 @@ class Bus():
Args:
prefix (str, optional): Prefix name of the bus. Defaults to "bus".
N (int, optional): Number of wires in the bus. Defaults to 1.
wires_list (list, optional): List of Wire objects used to clone one bus to another. Defaults to 0.
wires_list (list, optional): List of Wire objects used to clone one bus to another. Defaults to None.
out_bus (bool, optional): Specifies whether this Bus is an output bus of some previous component. Defaults to False.
signed (bool, optional): Specifies whether this Bus should consider signed numbers or not (used for C code generation). Defaults to False.
"""
@ -48,18 +48,23 @@ class Bus():
"""
return self.out_bus
def bus_extend(self, N: int, prefix: str = "bus"):
def bus_extend(self, N: int, prefix: str = "bus", last_wire_extend: bool = True):
"""Provides bus extension to contain more wires.
Args:
N (int): Number of wires in the bus. Defaults to 1.
prefix (str, optional): Prefix name of the bus. Defaults to "bus".
last_wire_extend (bool, optional): Specifies whether the last wire of the bus should be extended (connected) to all the extending wires. Defaults to True.
"""
# Checks if any extension is neccesarry and if so, proceeds to wire extend the bus
if self.N < N:
# Adding wires into current bus's wires list (wire names are concatenated from bus prefix and their index position inside the bus in square brackets)
self.bus += [Wire(name=prefix+f"[{i}]", prefix=prefix, index=i, parent_bus=self) for i in range(self.N, N)]
if last_wire_extend is True:
for w_index in range(self.N, N):
self.connect(bus_wire_index=w_index, inner_component_out_wire=self.get_wire(self.N - 1))
self.N = N
def get_wire(self, wire_index: int = 0):
"""Retrieves a wire from the bus by a given index.

BIN
bam.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

60
generate_axmuls.py Normal file
View File

@ -0,0 +1,60 @@
from ariths_gen.wire_components import (
Wire,
ConstantWireValue0,
ConstantWireValue1,
Bus
)
from ariths_gen.multi_bit_circuits.approximate_multipliers import (
UnsignedTruncatedMultiplier,
UnsignedBrokenArrayMultiplier
)
from ariths_gen.pdk import *
import os
from itertools import product
def open_file_with_folder(filename, mode):
d = os.path.dirname(filename)
if d:
os.makedirs(d, exist_ok = True)
return open(filename, mode)
if __name__ == "__main__":
# Optional use HA and FA technology from pdk45 library
#set_pdk45_library()
# 8-bit unsigned BAMs
root_path = "test_circuits/ax"
i = 0
for h in range(0, 8):
# v <= (8-1) + (8-2)
for v in range(h, 13):
i += 1
N=8
a = Bus(prefix="a", N=N)
b = Bus(prefix="b", N=N)
u_bam = UnsignedBrokenArrayMultiplier(a, b, name=f"f_u_bam{N}_h{h}_v{v}", horizontal_cut=h, vertical_cut=v)
u_bam.get_c_code_flat(open_file_with_folder(os.path.join(root_path, f"BAM/C/flat/f_u_bam{N}_h{h}_v{v}.c"), "w"))
u_bam.get_v_code_flat(open_file_with_folder(os.path.join(root_path, f"BAM/Verilog/flat/f_u_bam{N}_h{h}_v{v}.v"), "w"))
u_bam = UnsignedBrokenArrayMultiplier(a, b, name=f"h_u_bam{N}_h{h}_v{v}", horizontal_cut=h, vertical_cut=v)
u_bam.get_c_code_hier(open_file_with_folder(os.path.join(root_path, f"BAM/C/hier/h_u_bam{N}_h{h}_v{v}.c"), "w"))
u_bam.get_v_code_hier(open_file_with_folder(os.path.join(root_path, f"BAM/Verilog/hier/h_u_bam{N}_h{h}_v{v}.v"), "w"))
# 8-bit unsigned TMs
for i in range(0, 8):
N=8
a = Bus(prefix="a", N=N)
b = Bus(prefix="b", N=N)
u_tm = UnsignedTruncatedMultiplier(a, b, name=f"f_u_tm{N}_k{i}", truncation_cut=i)
u_tm.get_c_code_flat(open_file_with_folder(os.path.join(root_path, f"TM/C/flat/f_u_tm{N}_k{i}.c"), "w"))
u_tm.get_v_code_flat(open_file_with_folder(os.path.join(root_path, f"TM/Verilog/flat/f_u_tm{N}_k{i}.v"), "w"))
u_tm = UnsignedTruncatedMultiplier(a, b, name=f"h_u_tm{N}_k{i}", truncation_cut=i)
u_tm.get_c_code_hier(open_file_with_folder(os.path.join(root_path, f"TM/C/hier/h_u_tm{N}_k{i}.c"), "w"))
u_tm.get_v_code_hier(open_file_with_folder(os.path.join(root_path, f"TM/Verilog/hier/h_u_tm{N}_k{i}.v"), "w"))

View File

@ -17,8 +17,9 @@ class MAC(GeneralCircuit):
self.out.connect_bus(connecting_bus=self.add.out)
# usage
os.makedirs("test_circuits/mac", exist_ok=True)
mymac = MAC(Bus("a", 8), Bus("b", 8), Bus("acc", 16))
mymac.get_v_code_hier(open("test_circuits/mac/mac_hier.v", "w"))
mymac.get_c_code_hier(open("test_circuits/mac/mac_hier.c", "w"))
mymac.get_c_code_flat(open("test_circuits/mac/mac_flat.c", "w"))
if __name__ == "__main__":
os.makedirs("test_circuits/mac", exist_ok=True)
mymac = MAC(Bus("a", 8), Bus("b", 8), Bus("acc", 16))
mymac.get_v_code_hier(open("test_circuits/mac/mac_hier.v", "w"))
mymac.get_c_code_hier(open("test_circuits/mac/mac_hier.c", "w"))
mymac.get_c_code_flat(open("test_circuits/mac/mac_flat.c", "w"))

View File

@ -28,9 +28,54 @@ from ariths_gen.multi_bit_circuits.multipliers import (
SignedWallaceMultiplier,
)
from ariths_gen.multi_bit_circuits.approximate_multipliers import (
UnsignedTruncatedMultiplier,
SignedTruncatedMultiplier,
UnsignedBrokenArrayMultiplier,
SignedBrokenArrayMultiplier
)
import numpy as np
def test_unsigned_approxmul(values = False):
""" Test unsigned approximate multipliers """
N = 7
a = Bus(N=N, prefix="a")
b = Bus(N=N, prefix="b")
av = np.arange(2**N)
bv = av.reshape(-1, 1)
expected = av * bv
for c in [UnsignedBrokenArrayMultiplier, UnsignedTruncatedMultiplier]:
if c == UnsignedTruncatedMultiplier:
mul = c(a=a, b=b, truncation_cut=2)
elif c == UnsignedBrokenArrayMultiplier:
mul = c(a=a, b=b, horizontal_cut=1, vertical_cut=2)
r = mul(av, bv)
# WCE worst case error; used for approximate multiplier error measurement
WCE = np.amax(abs(np.subtract(r, expected)))
# WCRE worst case relative error; used for approximate multiplier error measurement
np.seterr(divide='ignore', invalid='ignore')
WCRE = np.max(np.nan_to_num(abs(np.subtract(r, expected)) / expected))
if isinstance(mul, UnsignedTruncatedMultiplier):
# WCE_TM(n,k) = (2^k - 1) * (2^(n+1) - 2^k - 1)
expected_WCE = (2 ** mul.truncation_cut - 1) * (2 ** (mul.a.N+1) - 2 ** mul.truncation_cut - 1)
elif isinstance(mul, UnsignedBrokenArrayMultiplier):
# WCE_BAM(n,h,v) = (2^n - 1) * {SUM_i0_to_h-1}(2^i) + 2^h * {SUM_i0_to_v-h-1}(2^(v-h) - 2^i)
sum_1 = sum([2**i for i in range(0, mul.horizontal_cut)])
sum_2 = sum([2**(mul.vertical_cut-mul.horizontal_cut) - 2**i for i in range(0, mul.vertical_cut-mul.horizontal_cut)])
expected_WCE = (2 ** mul.N - 1) * sum_1 + 2 ** mul.horizontal_cut * sum_2
# Test expected result
assert expected_WCE == WCE
if values is True:
np.testing.assert_array_equal(expected, r)
def test_unsigned_mul():
""" Test unsigned multipliers """

4
yosys_equiv_check.sh Normal file → Executable file
View File

@ -83,6 +83,8 @@ else
if [[ "$TOP_MODULE" != $(echo $BLIF_FILE | cut -c1-"$((${#BLIF_FILE}-5))") ]]; then
echo "Input files have different names! Do they describe the same circuit design?"
echo "For proper equivalence check, both designs should have the same name used for their filenames and also for their top module designs."
echo $TOP_MODULE
echo $(echo $BLIF_FILE | cut -c1-"$((${#BLIF_FILE}-5))")
echo
echo "Type -h | --help for more information."
exit 2
@ -136,4 +138,4 @@ else
equiv_simple
equiv_status -assert
"
fi
fi