mirror of
https://github.com/ehw-fit/ariths-gen.git
synced 2025-04-08 00:02:12 +01:00
Implemented CSA and Wallace tree multiplier composing of CSAs. Also did some code cleanup.
This commit is contained in:
parent
3c47407f80
commit
6f05db002e
@ -1,5 +1,6 @@
|
||||
from .arithmetic_circuit import (
|
||||
ArithmeticCircuit
|
||||
ArithmeticCircuit,
|
||||
ThreeInputArithmeticCircuit
|
||||
)
|
||||
|
||||
from .general_circuit import (GeneralCircuit )
|
||||
|
@ -38,9 +38,6 @@ class ArithmeticCircuit(GeneralCircuit):
|
||||
self.a = Bus(prefix=f"{a.prefix}", wires_list=a.bus)
|
||||
self.b = Bus(prefix=f"{b.prefix}", wires_list=b.bus)
|
||||
|
||||
# N output wires for given circuit
|
||||
self.out = Bus(self.prefix+"_out", out_N, out_bus=True, signed=self.signed)
|
||||
|
||||
""" C CODE GENERATION """
|
||||
def get_prototype_c(self):
|
||||
"""Generates C code function header to describe corresponding arithmetic circuit's interface in C code.
|
||||
@ -76,3 +73,193 @@ class ArithmeticCircuit(GeneralCircuit):
|
||||
f".outputs{self.out.get_wire_declaration_blif()}\n" + \
|
||||
f".names vdd\n1\n" + \
|
||||
f".names gnd\n0\n"
|
||||
|
||||
|
||||
class ThreeInputArithmeticCircuit(GeneralCircuit):
|
||||
"""Class represents a general three input arithmetic circuit and ensures its generation to various representations.
|
||||
|
||||
The __init__ method fills some mandatory attributes concerning arithmetic circuit
|
||||
that are later used for generation into various representations.
|
||||
"""
|
||||
|
||||
def __init__(self, a, b, c, prefix: str, name: str, out_N: int, inner_component: bool = False, one_bit_circuit: bool = False, signed: bool = False, **kwargs):
|
||||
super().__init__(prefix, name, out_N, inner_component, inputs=[a, b, c], signed=signed, **kwargs)
|
||||
if one_bit_circuit is False:
|
||||
if prefix == "":
|
||||
self.prefix = name
|
||||
else:
|
||||
self.prefix = prefix + "_" + name
|
||||
|
||||
self.inner_component = inner_component
|
||||
if self.inner_component is True:
|
||||
self.a = Bus(prefix=f"{self.prefix}_{a.prefix}", wires_list=a.bus)
|
||||
self.b = Bus(prefix=f"{self.prefix}_{b.prefix}", wires_list=b.bus)
|
||||
self.c = Bus(prefix=f"{self.prefix}_{c.prefix}", wires_list=c.bus)
|
||||
|
||||
if a.is_output_bus():
|
||||
self.a.connect_bus(connecting_bus=a)
|
||||
if b.is_output_bus():
|
||||
self.b.connect_bus(connecting_bus=b)
|
||||
if c.is_output_bus():
|
||||
self.c.connect_bus(connecting_bus=c)
|
||||
else:
|
||||
self.a = Bus(prefix=f"{a.prefix}", wires_list=a.bus)
|
||||
self.b = Bus(prefix=f"{b.prefix}", wires_list=b.bus)
|
||||
self.c = Bus(prefix=f"{c.prefix}", wires_list=c.bus)
|
||||
|
||||
""" C CODE GENERATION """
|
||||
def get_prototype_c(self):
|
||||
"""Generates C code function header to describe corresponding arithmetic circuit's interface in C code.
|
||||
|
||||
Returns:
|
||||
str: Function's name and parameters in C code.
|
||||
"""
|
||||
return f"{self.c_data_type} {self.prefix}({self.c_data_type} {self.a.prefix}, {self.c_data_type} {self.b.prefix}, {self.c_data_type} {self.c.prefix})" + "{" + "\n"
|
||||
|
||||
def get_function_block_c(self):
|
||||
"""Generates hierarchical C code representation of corresponding multi-bit arithmetic circuit used as function block in hierarchical circuit description.
|
||||
|
||||
Returns:
|
||||
str: Hierarchical C code of multi-bit arithmetic circuit's function block description.
|
||||
"""
|
||||
# Obtain proper circuit name with its bit width
|
||||
circuit_prefix = self.__class__(
|
||||
a=Bus("a"), b=Bus("b"), c=Bus("c")).prefix + str(self.N)
|
||||
circuit_block = self.__class__(a=Bus(N=self.N, prefix="a"), b=Bus(
|
||||
N=self.N, prefix="b"), c=Bus(
|
||||
N=self.N, prefix="c"), name=circuit_prefix)
|
||||
return f"{circuit_block.get_circuit_c()}\n\n"
|
||||
|
||||
def get_declaration_c_hier(self):
|
||||
"""Generates hierarchical C code declaration of corresponding subcomponent input/output wires inside the upper component.
|
||||
|
||||
Generates wires used to connect input/output values to/from invocation of the corresponding function block into inner wires present
|
||||
inside the upper component from which function block has been invoked.
|
||||
|
||||
Returns:
|
||||
str: Hierarchical C code of subcomponent arithmetic circuit's wires declaration.
|
||||
"""
|
||||
return f" {self.c_data_type} {self.a.prefix} = 0;\n" + \
|
||||
f" {self.c_data_type} {self.b.prefix} = 0;\n" + \
|
||||
f" {self.c_data_type} {self.c.prefix} = 0;\n" + \
|
||||
f" {self.c_data_type} {self.out.prefix} = 0;\n"
|
||||
|
||||
def get_out_invocation_c(self):
|
||||
"""Generates hierarchical C code invocation of corresponding arithmetic circuit's generated function block.
|
||||
|
||||
Assigns input values from other subcomponents into multi-bit input buses used as inputs for function block invocation.
|
||||
Assigns output values from invocation of the corresponding function block into inner wires present inside
|
||||
the upper component from which function block has been invoked.
|
||||
|
||||
Returns:
|
||||
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.__class__(a=Bus("a"), b=Bus("b"), c=Bus("c")).prefix + str(self.N)
|
||||
return self.a.return_bus_wires_values_c_hier() + self.b.return_bus_wires_values_c_hier() + self.c.return_bus_wires_values_c_hier() + \
|
||||
f" {self.out.prefix} = {circuit_type}({self.a.prefix}, {self.b.prefix}, {self.c.prefix});\n"
|
||||
|
||||
""" VERILOG CODE GENERATION """
|
||||
def get_prototype_v(self):
|
||||
"""Generates Verilog code module header to describe corresponding arithmetic circuit's interface in Verilog code.
|
||||
|
||||
Returns:
|
||||
str: Module's name and parameters in Verilog code.
|
||||
"""
|
||||
return f"module {self.prefix}(input [{self.N-1}:0] {self.a.prefix}, input [{self.N-1}:0] {self.b.prefix}, input [{self.N-1}:0] {self.c.prefix}, output [{self.out.N-1}:0] {self.out.prefix});\n"
|
||||
|
||||
def get_function_block_v(self):
|
||||
"""Generates hierarchical Verilog code representation of corresponding multi-bit arithmetic circuit used as function block in hierarchical circuit description.
|
||||
|
||||
Returns:
|
||||
str: Hierarchical Verilog code of multi-bit arithmetic circuit's function block description.
|
||||
"""
|
||||
# Obtain proper circuit name with its bit width
|
||||
circuit_prefix = self.__class__(
|
||||
a=Bus("a"), b=Bus("b"), c=Bus("c")).prefix + str(self.N)
|
||||
circuit_block = self.__class__(a=Bus(N=self.N, prefix="a"), b=Bus(
|
||||
N=self.N, prefix="b"), c=Bus(N=self.N, prefix="c"), name=circuit_prefix)
|
||||
return f"{circuit_block.get_circuit_v()}\n\n"
|
||||
|
||||
def get_declaration_v_hier(self):
|
||||
"""Generates hierarchical Verilog code declaration of corresponding subcomponent input/output wires inside the upper component.
|
||||
|
||||
Generates wires used to connect input/output values to/from invocation of the corresponding function block into inner wires present
|
||||
inside the upper component from which function block has been invoked.
|
||||
|
||||
Returns:
|
||||
str: Hierarchical Verilog code of subcomponent arithmetic circuit's wires declaration.
|
||||
"""
|
||||
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.c.N-1}:0] {self.c.prefix};\n" + \
|
||||
f" wire [{self.out.N-1}:0] {self.out.prefix};\n"
|
||||
|
||||
def get_out_invocation_v(self):
|
||||
"""Generates hierarchical Verilog code invocation of corresponding arithmetic circuit's generated function block.
|
||||
|
||||
Assigns input values from other subcomponents into multi-bit input buses used as inputs for function block invocation.
|
||||
Assigns output values from invocation of the corresponding function block into inner wires present inside
|
||||
the upper component from which function block has been invoked.
|
||||
|
||||
Returns:
|
||||
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.__class__(a=Bus("a"), b=Bus("b"), c=Bus("c")).prefix + str(self.N)
|
||||
circuit_block = self.__class__(a=Bus(N=self.N, prefix="a"), b=Bus(
|
||||
N=self.N, prefix="b"), c=Bus(
|
||||
N=self.N, prefix="c"), name=circuit_type)
|
||||
return self.a.return_bus_wires_values_v_hier() + self.b.return_bus_wires_values_v_hier() + self.c.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.c.prefix}({self.c.prefix}), .{circuit_block.out.prefix}({self.out.prefix}));\n"
|
||||
|
||||
""" BLIF CODE GENERATION """
|
||||
def get_declaration_blif(self):
|
||||
"""Generates flat Blif code declaration of input/output circuit wires.
|
||||
|
||||
Returns:
|
||||
str: Flat Blif code containing declaration of circuit's wires.
|
||||
"""
|
||||
if self.N == 1:
|
||||
return f".inputs {self.a.prefix} {self.b.prefix} {self.c.prefix}\n" + \
|
||||
f".outputs{self.out.get_wire_declaration_blif()}\n" + \
|
||||
f".names vdd\n1\n" + \
|
||||
f".names gnd\n0\n"
|
||||
else:
|
||||
return f".inputs{self.a.get_wire_declaration_blif()}{self.b.get_wire_declaration_blif()}{self.c.get_wire_declaration_blif()}\n" + \
|
||||
f".outputs{self.out.get_wire_declaration_blif()}\n" + \
|
||||
f".names vdd\n1\n" + \
|
||||
f".names gnd\n0\n"
|
||||
|
||||
def get_invocation_blif_hier(self):
|
||||
"""Generates hierarchical Blif code invocation of corresponding arithmetic circuit's generated function block.
|
||||
|
||||
Used for multi-bit subcomponent's modul invocation.
|
||||
|
||||
Returns:
|
||||
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.__class__(a=Bus("a"), b=Bus("b"), c=Bus("c")).prefix + str(self.N)
|
||||
return f"{self.a.get_wire_assign_blif(output=True)}" + \
|
||||
f"{self.b.get_wire_assign_blif(output=True)}" + \
|
||||
f"{self.c.get_wire_assign_blif(output=True)}" + \
|
||||
f".subckt {circuit_type}" + \
|
||||
"".join([f" a[{self.a.bus.index(w)}]={self.a.prefix}[{self.a.bus.index(w)}]" for w in self.a.bus]) + \
|
||||
"".join([f" b[{self.b.bus.index(w)}]={self.b.prefix}[{self.b.bus.index(w)}]" for w in self.b.bus]) + \
|
||||
"".join([f" c[{self.c.bus.index(w)}]={self.c.prefix}[{self.c.bus.index(w)}]" for w in self.c.bus]) + \
|
||||
"".join(
|
||||
[f" {circuit_type}_out[{self.out.bus.index(o)}]={o.name}" for o in self.out.bus]) + "\n"
|
||||
|
||||
def get_function_block_blif(self):
|
||||
"""Generates hierarchical Blif code representation of corresponding multi-bit arithmetic circuit used as function block in hierarchical circuit description.
|
||||
|
||||
Returns:
|
||||
str: Hierarchical Blif code of multi-bit arithmetic circuit's function block description.
|
||||
"""
|
||||
# Obtain proper circuit name with its bit width
|
||||
circuit_prefix = self.__class__(
|
||||
a=Bus("a"), b=Bus("b"), c=Bus("c")).prefix + str(self.N)
|
||||
circuit_block = self.__class__(a=Bus(N=self.N, prefix="a"), b=Bus(
|
||||
N=self.N, prefix="b"), c=Bus(N=self.N, prefix="c"), name=circuit_prefix)
|
||||
return f"{circuit_block.get_circuit_blif()}"
|
||||
|
@ -9,6 +9,7 @@ from ariths_gen.wire_components import (
|
||||
|
||||
from io import StringIO
|
||||
|
||||
|
||||
class GeneralCircuit():
|
||||
"""Class represents a general circuit and ensures its generation to various representations.
|
||||
|
||||
@ -16,7 +17,7 @@ class GeneralCircuit():
|
||||
that are later used for generation into various representations.
|
||||
"""
|
||||
|
||||
def __init__(self, prefix: str, name: str, out_N: int, inner_component: bool = False, inputs: list=[], signed: bool = False, outname = None):
|
||||
def __init__(self, prefix: str, name: str, out_N: int, inner_component: bool = False, inputs: list = [], signed: bool = False, outname=None):
|
||||
if prefix == "":
|
||||
self.prefix = name
|
||||
else:
|
||||
@ -32,7 +33,7 @@ class GeneralCircuit():
|
||||
self.circuit_gates = []
|
||||
self.c_data_type = "uint64_t"
|
||||
self.signed = signed
|
||||
self.pyc = None # Python compiled function
|
||||
self.pyc = None # Python compiled function
|
||||
|
||||
def __call__(self, *args):
|
||||
if not self.pyc:
|
||||
@ -41,7 +42,6 @@ class GeneralCircuit():
|
||||
|
||||
globs, locs = {}, {}
|
||||
exec(buf.getvalue(), globs, locs)
|
||||
#print(locs)
|
||||
self.pyc = locs[self.prefix]
|
||||
|
||||
return self.pyc(*args)
|
||||
@ -208,6 +208,9 @@ class GeneralCircuit():
|
||||
(w, f"{w.name}", self.save_wire_id(wire=w))) for w in self.a.bus]
|
||||
[self.circuit_wires.append(
|
||||
(w, f"{w.name}", self.save_wire_id(wire=w))) for w in self.b.bus]
|
||||
if hasattr(self, 'c'):
|
||||
[self.circuit_wires.append(
|
||||
(w, f"{w.name}", self.save_wire_id(wire=w))) for w in self.c.bus]
|
||||
else:
|
||||
self.circuit_wires.append(
|
||||
(self.a, f"{self.a.name}", self.save_wire_id(wire=self.a)))
|
||||
@ -397,18 +400,15 @@ class GeneralCircuit():
|
||||
Returns:
|
||||
str: Hierarchical C code initialization of arithmetic circuit wires.
|
||||
"""
|
||||
return "".join([c.get_gate_invocation_c() if isinstance(c, TwoInputLogicGate) else c.get_out_invocation_c(circuit_prefix=self.prefix) for c in self.components])
|
||||
return "".join([c.get_gate_invocation_c() if isinstance(c, TwoInputLogicGate) else c.get_out_invocation_c() for c in self.components])
|
||||
|
||||
def get_out_invocation_c(self, circuit_prefix: str):
|
||||
def get_out_invocation_c(self):
|
||||
"""Generates hierarchical C code invocation of corresponding arithmetic circuit's generated function block.
|
||||
|
||||
Assigns input values from other subcomponents into multi-bit input buses used as inputs for function block invocation.
|
||||
Assigns output values from invocation of the corresponding function block into inner wires present inside
|
||||
the upper component from which function block has been invoked.
|
||||
|
||||
Args:
|
||||
circuit_prefix (str): Prefix name of the upper component from which function block is being invoked.
|
||||
|
||||
Returns:
|
||||
str: Hierarchical C code of subcomponent's C function invocation and output assignment.
|
||||
"""
|
||||
@ -549,18 +549,15 @@ class GeneralCircuit():
|
||||
Returns:
|
||||
str: Hierarchical Verilog code initialization of arithmetic circuit wires.
|
||||
"""
|
||||
return "".join([c.get_gate_invocation_v() if isinstance(c, TwoInputLogicGate) else c.get_out_invocation_v(circuit_prefix=self.prefix) for c in self.components])
|
||||
return "".join([c.get_gate_invocation_v() if isinstance(c, TwoInputLogicGate) else c.get_out_invocation_v() for c in self.components])
|
||||
|
||||
def get_out_invocation_v(self, circuit_prefix: str):
|
||||
def get_out_invocation_v(self):
|
||||
"""Generates hierarchical Verilog code invocation of corresponding arithmetic circuit's generated function block.
|
||||
|
||||
Assigns input values from other subcomponents into multi-bit input buses used as inputs for function block invocation.
|
||||
Assigns output values from invocation of the corresponding function block into inner wires present inside
|
||||
the upper component from which function block has been invoked.
|
||||
|
||||
Args:
|
||||
circuit_prefix (str): Prefix name of the upper component from which function block is being invoked.
|
||||
|
||||
Returns:
|
||||
str: Hierarchical Verilog code of subcomponent's module invocation and output assignment.
|
||||
"""
|
||||
@ -659,16 +656,13 @@ class GeneralCircuit():
|
||||
Returns:
|
||||
str: Hierarchical Blif code containing invocations of inner subcomponents function blocks.
|
||||
"""
|
||||
return "".join(c.get_invocation_blif_hier(circuit_prefix=self.prefix) for c in self.components)
|
||||
return "".join(c.get_invocation_blif_hier() for c in self.components)
|
||||
|
||||
def get_invocation_blif_hier(self, circuit_prefix: str):
|
||||
def get_invocation_blif_hier(self):
|
||||
"""Generates hierarchical Blif code invocation of corresponding arithmetic circuit's generated function block.
|
||||
|
||||
Used for multi-bit subcomponent's modul invocation.
|
||||
|
||||
Args:
|
||||
circuit_prefix (str): Prefix name of the upper component from which function block is being invoked.
|
||||
|
||||
Returns:
|
||||
str: Hierarchical Blif code of subcomponent's model invocation and output assignment.
|
||||
"""
|
||||
@ -741,7 +735,7 @@ class GeneralCircuit():
|
||||
str: CGP chromosome parameters of described arithmetic circuit.
|
||||
"""
|
||||
self.circuit_gates = self.get_circuit_gates()
|
||||
return f"{{{self.a.N+self.a.N},{self.out.N},1,{len(self.circuit_gates)},2,1,0}}"
|
||||
return f"{{{sum(input_bus.N for input_bus in self.inputs)},{self.out.N},1,{len(self.circuit_gates)},2,1,0}}"
|
||||
|
||||
def get_triplets_cgp(self):
|
||||
"""Generates list of logic gate triplets (first input wire, second input wire, logic gate function) using wires unique position indexes within the described circuit.
|
||||
|
@ -46,13 +46,13 @@ class MultiplierCircuit(ArithmeticCircuit):
|
||||
"""
|
||||
# To get the index of previous row's connecting adder and its generated pp
|
||||
if mult_type == "bam":
|
||||
#TODO alter to be more compact
|
||||
# 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
|
||||
# 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])
|
||||
@ -65,8 +65,6 @@ class MultiplierCircuit(ArithmeticCircuit):
|
||||
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:
|
||||
index = index-2
|
||||
@ -103,26 +101,71 @@ class MultiplierCircuit(ArithmeticCircuit):
|
||||
if d >= initial_value:
|
||||
return stage, max_height
|
||||
|
||||
def init_column_heights(self, signed: bool = False):
|
||||
"""Creates appropriate number of partial product columns along with filling them with corresponding number of bit pairs.
|
||||
def init_row_lengths(self):
|
||||
"""Creates appropriate number of partial product rows along with filling them with corresponding number of bit pairs.
|
||||
|
||||
Returns:
|
||||
list: List of partial product rows with their bit pairs.
|
||||
"""
|
||||
rows = [[] for _ in range(self.N)]
|
||||
rows = [self.add_row_wires(row=row, row_index=rows.index(row)) for row in rows]
|
||||
return rows
|
||||
|
||||
def add_row_wires(self, row: list, row_index: int):
|
||||
"""Fills circuit's partial product row with corresponding bit pairs.
|
||||
|
||||
Args:
|
||||
signed (bool, optional): Specify whether pp columns bit pairs should perform signed multiplication or not. Defaults to False.
|
||||
row (list): List representing row of partial product bits.
|
||||
row_index (int): Index of partial products row.
|
||||
|
||||
Returns:
|
||||
list: Updated row list containing corresponding number of input bit pairs to form proper pp row.
|
||||
"""
|
||||
# Number of partial products present in the row (should be equal to circuit's input bus size)
|
||||
row_pp_count = self.N
|
||||
# Adding neccessary number of lists (based on number of bits in the row – stored in `row_pp_count`)
|
||||
# to row that each represent individual bit pairs for described row (these bit pairs are then combined in AND/NAND gates)
|
||||
[row.append([]) for _ in range(row_pp_count)]
|
||||
|
||||
# Filling row bit pair lists with appropriate bits
|
||||
[row[index].append(self.a.get_wire(index)) for index in range(row_pp_count)]
|
||||
[row[index].append(self.b.get_wire(row_index)) for index in range(row_pp_count)]
|
||||
|
||||
# Converting unsigned rows of pp bit pair lists into AND gates
|
||||
if self.signed is False:
|
||||
row[0:] = [self.add_component(AndGate(a=row[i][0], b=row[i][1], prefix=self.prefix+'_and_'+str(row[i][0].index)+'_'+str(row[i][1].index), parent_component=self)).out for i in range(row_pp_count)]
|
||||
# Converting signed rows of pp bit pair lists into AND/NAND gates (based on Baugh-Wooley multiplication algorithm)
|
||||
else:
|
||||
# Partial product bit pairs of all rows (expect for the last one) are connected to AND gates, besides the last pp bit pair in each row that is connected to a NAND gate
|
||||
if row_index != self.N-1:
|
||||
row[0:row_pp_count-1] = [self.add_component(AndGate(a=row[i][0], b=row[i][1], prefix=self.prefix+'_and_'+str(row[i][0].index)+'_'+str(row[i][1].index), parent_component=self)).out for i in range(row_pp_count-1)]
|
||||
|
||||
row[row_pp_count-1] = self.add_component(NandGate(a=row[row_pp_count-1][0], b=row[row_pp_count-1][1], prefix=self.prefix+'_nand_'+str(row[row_pp_count-1][0].index)+'_'+str(row[row_pp_count-1][1].index), parent_component=self)).out
|
||||
# Partial product bit pairs of the last row are connected to NAND gates besides the last pp pair that is connected to an AND gate
|
||||
else:
|
||||
row[0:row_pp_count-1] = [self.add_component(NandGate(a=row[i][0], b=row[i][1], prefix=self.prefix+'_nand_'+str(row[i][0].index)+'_'+str(row[i][1].index), parent_component=self)).out for i in range(row_pp_count-1)]
|
||||
|
||||
row[row_pp_count-1] = self.add_component(AndGate(a=row[row_pp_count-1][0], b=row[row_pp_count-1][1], prefix=self.prefix+'_and_'+str(row[row_pp_count-1][0].index)+'_'+str(row[row_pp_count-1][1].index), parent_component=self)).out
|
||||
|
||||
pp_row_wires = Bus(prefix=f"pp_row{row_index}", wires_list=row)
|
||||
return pp_row_wires
|
||||
|
||||
def init_column_heights(self):
|
||||
"""Creates appropriate number of partial product columns along with filling them with corresponding number of bit pairs.
|
||||
|
||||
Returns:
|
||||
list: List of partial product columns with their bit pairs.
|
||||
"""
|
||||
columns = [[num] if num <= self.N else [num - (num - self.N)*2] for num in range(1, self.out.N)]
|
||||
columns = [self.add_column_wires(column=col, column_index=columns.index(col), signed=signed) for col in columns]
|
||||
columns = [self.add_column_wires(column=col, column_index=columns.index(col)) for col in columns]
|
||||
return columns
|
||||
|
||||
def add_column_wires(self, column: list, column_index: int, signed: bool):
|
||||
def add_column_wires(self, column: list, column_index: int):
|
||||
"""Fills circuit's partial product column with corresponding bit pairs.
|
||||
|
||||
Args:
|
||||
column (list): List representing column of partial product bits.
|
||||
column_index (int): Index of partial products column.
|
||||
signed (bool): Specify whether pp columns bit pairs should perform signed multiplication or not.
|
||||
|
||||
Returns:
|
||||
list: Updated column list containing corresponding number of input bit pairs to form proper pp column.
|
||||
@ -139,7 +182,7 @@ class MultiplierCircuit(ArithmeticCircuit):
|
||||
[column[index-(self.a.N-1-column[0])].append(self.b.get_wire(index)) for index in range(self.a.N-column[0], self.a.N)]
|
||||
|
||||
# Converting unsigned column pp bit pair lists into AND gates
|
||||
if signed is False:
|
||||
if self.signed is False:
|
||||
column[1:] = [AndGate(a=column[i][0], b=column[i][1], prefix=self.prefix+'_and_'+str(column[i][0].index)+'_'+str(column[i][1].index), parent_component=self) for i in range(1, len(column))]
|
||||
# Converting signed column pp bit pair lists into AND/NAND gates (based on Baugh-Wooley multiplication algorithm)
|
||||
else:
|
||||
|
@ -17,3 +17,8 @@ from ariths_gen.multi_bit_circuits.adders.carry_skip_adder import (
|
||||
UnsignedCarrySkipAdder,
|
||||
SignedCarrySkipAdder
|
||||
)
|
||||
|
||||
from ariths_gen.multi_bit_circuits.adders.carry_save_adder import (
|
||||
CarrySaveAdderComponent,
|
||||
UnsignedCarrySaveAdder
|
||||
)
|
||||
|
155
ariths_gen/multi_bit_circuits/adders/carry_save_adder.py
Normal file
155
ariths_gen/multi_bit_circuits/adders/carry_save_adder.py
Normal file
@ -0,0 +1,155 @@
|
||||
from ariths_gen.wire_components import (
|
||||
Wire,
|
||||
ConstantWireValue0,
|
||||
ConstantWireValue1,
|
||||
Bus
|
||||
)
|
||||
from ariths_gen.core.arithmetic_circuits import (
|
||||
ArithmeticCircuit,
|
||||
ThreeInputArithmeticCircuit,
|
||||
MultiplierCircuit
|
||||
)
|
||||
from ariths_gen.one_bit_circuits.one_bit_components import (
|
||||
HalfAdder,
|
||||
PGLogicBlock,
|
||||
FullAdder,
|
||||
FullAdderPG
|
||||
)
|
||||
from ariths_gen.one_bit_circuits.logic_gates import (
|
||||
AndGate,
|
||||
NandGate,
|
||||
OrGate,
|
||||
NorGate,
|
||||
XorGate,
|
||||
XnorGate,
|
||||
NotGate
|
||||
)
|
||||
from ariths_gen.multi_bit_circuits.adders import (
|
||||
UnsignedCarryLookaheadAdder,
|
||||
UnsignedPGRippleCarryAdder,
|
||||
UnsignedRippleCarryAdder,
|
||||
SignedCarryLookaheadAdder,
|
||||
SignedPGRippleCarryAdder,
|
||||
SignedRippleCarryAdder
|
||||
)
|
||||
|
||||
|
||||
class CarrySaveAdderComponent(ThreeInputArithmeticCircuit):
|
||||
"""Class representing carry save adder component.
|
||||
|
||||
The carry save adder component is especially useful when constructing tree multiplier architectures to reduce the propagation delay as opposed to traditional implementation of tree multipliers with half/full adders.
|
||||
|
||||
The circuit is composed of full adders that operate paralelly and each take 3 input operands.
|
||||
The final output bus is composed of sum and carry bits of its corresponding contained full adders.
|
||||
```
|
||||
C3 B3 A3 C2 B2 A2 C1 B1 A1 C0 B0 A0
|
||||
│ │ │ │ │ │ │ │ │ │ │ │
|
||||
┌▼─▼─▼─┐ ┌▼─▼─▼─┐ ┌▼─▼─▼─┐ ┌▼─▼─▼─┐
|
||||
│ │ │ │ │ │ │ │
|
||||
│ FA │ │ FA │ │ FA │ │ FA │
|
||||
│ │ │ │ │ │ │ │
|
||||
└─┬──┬─┘ └─┬──┬─┘ └─┬──┬─┘ └─┬──┬─┘
|
||||
│c3│s3 │c2│s2 │c1│s1 │c0│s0
|
||||
└──┼──┐ ┌─┘ │┌──────┼──┼───────┘ │
|
||||
│ │ │ ┌─┼┼──────┘ └───────┐ │
|
||||
│ │ │ │ └┼──────────────┐ │ │
|
||||
└──┼──┼──┼──┼───────────┐ │ │ │
|
||||
│ │ │ │ 0 0/1│ │ │ │
|
||||
│ │ │ │ │ │ │ │ │ │
|
||||
o9 o8 o7 o6 o5 o4 o3 o2 o1 o0
|
||||
c3 c2 c1 c0 0 0/1 s3 s2 s1 s0
|
||||
```
|
||||
|
||||
Description of the __init__ method.
|
||||
|
||||
Args:
|
||||
a (Bus): First input bus.
|
||||
b (Bus): Second input bus.
|
||||
c (Bus): Third input bus.
|
||||
prefix (str, optional): Prefix name of csa component. Defaults to "".
|
||||
name (str, optional): Name of csa component. Defaults to "csa_component".
|
||||
signed (bool, optional): Specifies whether the component should be used in (un)signed context (used during the construction of tree multipliers). Defaults to False.
|
||||
"""
|
||||
def __init__(self, a: Bus, b: Bus, c: Bus, prefix: str = "", name: str = "csa_component", signed: bool = False, **kwargs):
|
||||
self.N = max(a.N, b.N, c.N)
|
||||
super().__init__(a=a, b=b, c=c, prefix=prefix, name=name, out_N=(2*self.N)+2, signed=signed, **kwargs)
|
||||
|
||||
bus_extension_wire = ConstantWireValue1() if self.signed is True else ConstantWireValue0()
|
||||
self.a.bus_extend(N=self.N, prefix=a.prefix, desired_extension_wire=bus_extension_wire)
|
||||
self.b.bus_extend(N=self.N, prefix=b.prefix, desired_extension_wire=bus_extension_wire)
|
||||
self.c.bus_extend(N=self.N, prefix=c.prefix, desired_extension_wire=bus_extension_wire)
|
||||
|
||||
self.sum_bits = Bus(prefix=self.prefix + "_sums", N=int(self.out.N/2))
|
||||
self.carry_bits = Bus(prefix=self.prefix + "_carries", N=int(self.out.N/2))
|
||||
self.sum_bits.connect(-1, bus_extension_wire)
|
||||
self.carry_bits.connect(0, ConstantWireValue0())
|
||||
|
||||
# Gradual addition of 1-bit CSA components (FAs are used)
|
||||
for input_index in range(self.N):
|
||||
obj_adder = FullAdder(self.a.get_wire(input_index), self.b.get_wire(input_index), self.c.get_wire(input_index), prefix=self.prefix+"_fa"+str(input_index))
|
||||
self.add_component(obj_adder)
|
||||
|
||||
self.sum_bits.connect(input_index, obj_adder.get_sum_wire())
|
||||
self.carry_bits.connect(input_index+1, obj_adder.get_carry_wire())
|
||||
|
||||
[self.out.connect(o, self.sum_bits.get_wire(o)) for o in range(0, int(self.out.N/2))]
|
||||
[self.out.connect(o, self.carry_bits.get_wire(o-int(self.out.N/2))) for o in range(int(self.out.N/2), self.out.N)]
|
||||
|
||||
|
||||
class UnsignedCarrySaveAdder(ThreeInputArithmeticCircuit):
|
||||
"""Class representing unsigned carry save adder.
|
||||
|
||||
Unsigned carry save adder represents 3 input N-bit unsigned adder which is composed of
|
||||
N one bit full adders, that are not interconnected as in for example the ripple carry adder,
|
||||
but instead operate parallelly.
|
||||
|
||||
The sum and carry bits of the individual full adders are connected to a multi bit adder to sum the final result.
|
||||
```
|
||||
C3 B3 A3 C2 B2 A2 C1 B1 A1 C0 B0 A0
|
||||
│ │ │ │ │ │ │ │ │ │ │ │
|
||||
┌▼─▼─▼─┐ ┌▼─▼─▼─┐ ┌▼─▼─▼─┐ ┌▼─▼─▼─┐
|
||||
│ │ │ │ │ │ │ │
|
||||
│ FA │ │ FA │ │ FA │ │ FA │
|
||||
│ │ │ │ │ │ │ │
|
||||
└─┬──┬─┘ └─┬──┬─┘ └─┬──┬─┘ └─┬──┬─┘
|
||||
┌┘c3│s3┌────┘c2│s2┌────┘c1│s1┌────┘c0│s0
|
||||
0 │ │ │ │ │ │ │ │ 0
|
||||
│Y4│X4 │Y3│X3 │Y2│X2 │Y1│X1 │Y0│X0
|
||||
┌─▼──▼───▼──▼───────▼──▼───────▼──▼───────▼──▼─┐
|
||||
│ 5-bit multi bit adder │
|
||||
│ │
|
||||
└┬────────┬────────┬────────┬────────┬────────┬┘
|
||||
Cout S4 S3 S2 S1 S0
|
||||
```
|
||||
|
||||
Description of the __init__ method.
|
||||
|
||||
Args:
|
||||
a (Bus): First input bus.
|
||||
b (Bus): Second input bus.
|
||||
c (Bus): Third input bus.
|
||||
prefix (str, optional): Prefix name of unsigned csa. Defaults to "".
|
||||
name (str, optional): Name of unsigned csa. Defaults to "u_csa".
|
||||
unsigned_adder_class_name (str, optional): Unsigned multi bit adder used to obtain final sum. Defaults to UnsignedCarryLookaheadAdder.
|
||||
"""
|
||||
def __init__(self, a: Bus, b: Bus, c: Bus, prefix: str = "", name: str = "u_csa", unsigned_adder_class_name: str = UnsignedCarryLookaheadAdder, **kwargs):
|
||||
self.N = max(a.N, b.N, c.N)
|
||||
super().__init__(a=a, b=b, c=c, 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.c.bus_extend(N=self.N, prefix=c.prefix)
|
||||
|
||||
csa_adder = CarrySaveAdderComponent(a=self.a, b=self.b, c=self.c, prefix=prefix, name="csa_comp" + str(self.N), inner_component=True)
|
||||
self.add_component(csa_adder)
|
||||
|
||||
# Obtain proper adder name with its bit width
|
||||
adder_name = unsigned_adder_class_name(a=self.a, b=self.b).prefix + str(self.N+1)
|
||||
adder_a = Bus(prefix="a", N=csa_adder.sum_bits.N)
|
||||
adder_b = Bus(prefix="b", N=csa_adder.carry_bits.N)
|
||||
adder_a.connect_bus(connecting_bus=csa_adder.out, end_connection_pos=int(csa_adder.out.N/2))
|
||||
adder_b.connect_bus(connecting_bus=csa_adder.out, start_connection_pos=int(csa_adder.out.N/2), end_connection_pos=csa_adder.out.N, offset=int(csa_adder.out.N/2))
|
||||
final_adder = unsigned_adder_class_name(a=adder_a, b=adder_b, prefix=self.prefix, name=adder_name, inner_component=True)
|
||||
self.add_component(final_adder)
|
||||
self.out.connect_bus(connecting_bus=final_adder.out)
|
@ -1,4 +1,3 @@
|
||||
|
||||
from ariths_gen.wire_components import (
|
||||
Wire,
|
||||
ConstantWireValue0,
|
||||
@ -27,15 +26,13 @@ from ariths_gen.one_bit_circuits.logic_gates import (
|
||||
XnorGate,
|
||||
NotGate
|
||||
)
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class UnsignedCGPCircuit(GeneralCircuit):
|
||||
""" Circuit that loads CGP code and is able to export it to C/verilog/Blif/CGP """
|
||||
"""Unsigned circuit variant that loads CGP code and is able to export it to C/verilog/Blif/CGP."""
|
||||
|
||||
def __init__(self, code: str, input_widths: list, prefix: str = "", name: str = "cgp", **kwargs):
|
||||
|
||||
cgp_prefix, cgp_core, cgp_outputs = re.match(
|
||||
r"{(.*)}(.*)\(([^()]+)\)", code).groups()
|
||||
|
||||
@ -43,25 +40,21 @@ class UnsignedCGPCircuit(GeneralCircuit):
|
||||
int, cgp_prefix.split(","))
|
||||
|
||||
assert sum(
|
||||
input_widths) == c_in, f"CGP input widht {c_in} doesn't match input_widhts {input_widths}"
|
||||
#assert c_rows == 1, f"Only one-row CGP is supported {c_rows}x{c_cols}"
|
||||
input_widths) == c_in, f"CGP input width {c_in} doesn't match input_widths {input_widths}"
|
||||
|
||||
inputs = [Bus(N=bw, prefix=f"input_{chr(i)}")
|
||||
for i, bw in enumerate(input_widths, start=0x61)]
|
||||
#vals = Bus(N=c_rows * c_cols, prefix=f"{prefix}_data")
|
||||
|
||||
# adding values to the list
|
||||
# Adding values to the list
|
||||
self.vals = {}
|
||||
j = 2 # start from two, 0=false, 1 = true
|
||||
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
|
||||
|
||||
|
||||
super().__init__(prefix=prefix, name=name, out_N=c_out, inputs=inputs, **kwargs)
|
||||
|
||||
cgp_core = cgp_core.split(")(")
|
||||
|
||||
i = 0
|
||||
@ -74,38 +67,37 @@ class UnsignedCGPCircuit(GeneralCircuit):
|
||||
comp_set = dict(prefix=f"{self.prefix}_core_{i:03d}", parent_component=self)
|
||||
|
||||
a, b = self._get_wire(in_a), self._get_wire(in_b)
|
||||
if fn == 0: # identity
|
||||
if fn == 0: # IDENTITY
|
||||
o = a
|
||||
elif fn == 1: # not
|
||||
elif fn == 1: # NOT
|
||||
o = self.add_component(NotGate(a, **comp_set)).out
|
||||
elif fn == 2: # and
|
||||
elif fn == 2: # AND
|
||||
o = self.add_component(AndGate(a, b, **comp_set)).out
|
||||
elif fn == 3: # or
|
||||
elif fn == 3: # OR
|
||||
o = self.add_component(OrGate(a, b, **comp_set)).out
|
||||
elif fn == 4: # xor
|
||||
elif fn == 4: # XOR
|
||||
o = self.add_component(XorGate(a, b, **comp_set)).out
|
||||
elif fn == 5: # nand
|
||||
elif fn == 5: # NAND
|
||||
o = self.add_component(NandGate(a, b, **comp_set)).out
|
||||
elif fn == 6: # nor
|
||||
elif fn == 6: # NOR
|
||||
o = self.add_component(NorGate(a, b, **comp_set)).out
|
||||
elif fn == 7: # xnor
|
||||
elif fn == 7: # XNOR
|
||||
o = self.add_component(XnorGate(a, b, **comp_set)).out
|
||||
elif fn == 8: # true
|
||||
elif fn == 8: # TRUE
|
||||
o = ConstantWireValue1()
|
||||
elif fn == 9: # false
|
||||
elif fn == 9: # FALSE
|
||||
o = ConstantWireValue0()
|
||||
|
||||
assert i not in self.vals
|
||||
self.vals[i] = o
|
||||
|
||||
# output connection
|
||||
# Output connection
|
||||
for i, o in enumerate(map(int, cgp_outputs.split(","))):
|
||||
w = self._get_wire(o)
|
||||
#print(i, o, w, w.name)
|
||||
self.out.connect(i, w)
|
||||
|
||||
@staticmethod
|
||||
def get_inputs_outputs(code : str):
|
||||
def get_inputs_outputs(code: str):
|
||||
cgp_prefix, cgp_core, cgp_outputs = re.match(
|
||||
r"{(.*)}(.*)\(([^()]+)\)", code).groups()
|
||||
|
||||
@ -114,7 +106,6 @@ class UnsignedCGPCircuit(GeneralCircuit):
|
||||
|
||||
return c_in, c_out
|
||||
|
||||
|
||||
def _get_wire(self, i):
|
||||
if i == 0:
|
||||
return ConstantWireValue0()
|
||||
@ -122,11 +113,9 @@ class UnsignedCGPCircuit(GeneralCircuit):
|
||||
return ConstantWireValue1()
|
||||
return self.vals[i]
|
||||
|
||||
#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)
|
||||
|
||||
class SignedCGPCircuit(UnsignedCGPCircuit):
|
||||
"""Signed circuit variant that loads CGP code and is able to export it to C/verilog/Blif/CGP."""
|
||||
def __init__(self, code: str, input_widths: list, prefix: str = "", name: str = "cgp", **kwargs):
|
||||
super().__init__(code=code, input_widths=input_widths, prefix=prefix, name=name, signed=True, **kwargs)
|
||||
self.c_data_type = "int64_t"
|
||||
self.c_data_type = "int64_t"
|
||||
|
@ -12,3 +12,8 @@ from ariths_gen.multi_bit_circuits.multipliers.dadda_multiplier import (
|
||||
UnsignedDaddaMultiplier,
|
||||
SignedDaddaMultiplier
|
||||
)
|
||||
|
||||
from ariths_gen.multi_bit_circuits.multipliers.wallace_csa_multiplier import (
|
||||
UnsignedWallaceCSAMultiplier,
|
||||
SignedWallaceCSAMultiplier
|
||||
)
|
||||
|
@ -165,7 +165,7 @@ class SignedDaddaMultiplier(MultiplierCircuit):
|
||||
# Get starting stage and maximum possible column height
|
||||
self.stage, self.d = self.get_maximum_height(initial_value=min(self.a.N, self.b.N))
|
||||
# Initialize all columns partial products forming AND/NAND gates matrix based on Baugh-Wooley multiplication
|
||||
self.columns = self.init_column_heights(signed=True)
|
||||
self.columns = self.init_column_heights()
|
||||
|
||||
# Not used for 1 bit multiplier
|
||||
if self.N != 1:
|
||||
|
@ -0,0 +1,214 @@
|
||||
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.adders import (
|
||||
CarrySaveAdderComponent,
|
||||
UnsignedCarryLookaheadAdder
|
||||
)
|
||||
|
||||
|
||||
class UnsignedWallaceCSAMultiplier(MultiplierCircuit):
|
||||
"""Class representing unsigned wallace multiplier composed of carry save adder components.
|
||||
|
||||
Unsigned wallace multiplier represents fast N-bit multiplier which utilizes
|
||||
the functionality of wallace tree reduction algorithm proposed by Chris Wallace.
|
||||
|
||||
First partial products are calculated for each bit pair that form the partial product multiplication rows.
|
||||
This implementation uses carry save adder components to efficiently implement reduction of partial products utilizing the parallelism of the carry save adders.
|
||||
At last the reduced pairs are inserted into chosen multi bit unsigned adder to execute their summation and obtain the final output bits.
|
||||
|
||||
Wallace tree algorithm is described more in detail here:
|
||||
https://en.wikipedia.org/wiki/Wallace_tree
|
||||
|
||||
It presents a faster version of multiplier opposed to the conventional architectures that are composed of interconnected half/full adders.
|
||||
```
|
||||
PP7 PP6 PP5 PP4 PP3 PP2 PP1 PP0
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ ┌▼──▼──▼┐ ┌▼──▼──▼┐
|
||||
│ │ │ CSA │ │ CSA │
|
||||
│ │ └─┬───┬─┘ └─┬───┬─┘
|
||||
│ │ │c1 │s1 │c0 │s0
|
||||
└┐ │ ┌──┘ └────┐ │ ┌┘
|
||||
┌▼──▼──▼┐ ┌▼──▼──▼┐
|
||||
│ CSA │ │ CSA │
|
||||
└─┬───┬─┘ └─┬───┬─┘
|
||||
│c4 │s4 ┌───────┘c3 │s3
|
||||
│ └──┐ │ ┌────────┘
|
||||
│ ┌▼──▼──▼┐
|
||||
│ │ CSA │
|
||||
│ └─┬───┬─┘
|
||||
│ ┌────┘c5 │s5
|
||||
│ │ ┌─────┘
|
||||
┌▼──▼──▼┐
|
||||
│ CSA │
|
||||
└─┬───┬─┘
|
||||
│c6 │s6
|
||||
┌─▼───▼─┐
|
||||
│ CPA │
|
||||
└───┬───┘
|
||||
o
|
||||
```
|
||||
|
||||
Description of the __init__ method.
|
||||
|
||||
Args:
|
||||
a (Bus): First input bus.
|
||||
b (Bus): Second input bus.
|
||||
prefix (str, optional): Prefix name of unsigned csa wallace multiplier. Defaults to "".
|
||||
name (str, optional): Name of unsigned csa wallace multiplier. Defaults to "u_wallaceCSA_cla".
|
||||
unsigned_adder_class_name (str, optional): Unsigned multi bit adder used to obtain final sums of products. Defaults to UnsignedCarryLookaheadAdder.
|
||||
"""
|
||||
def __init__(self, a: Bus, b: Bus, prefix: str = "", name: str = "u_wallaceCSA_cla", unsigned_adder_class_name: str = UnsignedCarryLookaheadAdder, **kwargs):
|
||||
self.N = max(a.N, b.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)
|
||||
|
||||
# Initialize all rows partial products forming AND gates matrix
|
||||
self.rows = self.init_row_lengths()
|
||||
|
||||
# Zero extension of partial product rows
|
||||
for i in range(0, len(self.rows)):
|
||||
self.rows[i] = Bus(prefix=self.rows[i].prefix, wires_list=[ConstantWireValue0() for _ in range(0, i)] + self.rows[i].bus)
|
||||
|
||||
while len(self.rows) > 2:
|
||||
# Gradual creation of unsigned csa adder components to reduce the pp rows to the total count of 2
|
||||
pp_index = 0
|
||||
while pp_index < len(self.rows) and (pp_index+2) < len(self.rows):
|
||||
csa_reduction = CarrySaveAdderComponent(a=self.rows[pp_index], b=self.rows[pp_index+1], c=self.rows[pp_index+2], prefix=self.prefix+"_csa"+str(self.get_instance_num(cls=CarrySaveAdderComponent)), inner_component=True)
|
||||
self.add_component(csa_reduction)
|
||||
|
||||
# 3 pp rows have been reduced to 2
|
||||
[self.rows.pop(pp_index) for i in range(3)]
|
||||
|
||||
# Append rows of sum and carry results from csa calculation
|
||||
csa_sums_N = self.out.N if csa_reduction.sum_bits.N > self.out.N-1 else csa_reduction.sum_bits.N
|
||||
csa_sums = Bus(prefix=self.prefix+"_csa_s"+str(self.get_instance_num(cls=CarrySaveAdderComponent)), N=csa_sums_N)
|
||||
csa_sums.connect_bus(connecting_bus=csa_reduction.out, end_connection_pos=csa_sums_N)
|
||||
|
||||
csa_carries_N = self.out.N if csa_reduction.carry_bits.N > self.out.N-1 else csa_reduction.carry_bits.N
|
||||
csa_carries = Bus(prefix=self.prefix+"_csa_c"+str(self.get_instance_num(cls=CarrySaveAdderComponent)), N=csa_carries_N)
|
||||
csa_carries.connect_bus(connecting_bus=csa_reduction.out, start_connection_pos=int(csa_reduction.out.N/2), end_connection_pos=int(csa_reduction.out.N/2)+csa_carries.N, offset=int(csa_reduction.out.N/2))
|
||||
|
||||
self.rows.insert(pp_index, csa_carries)
|
||||
self.rows.insert(pp_index, csa_sums)
|
||||
|
||||
# Update of the number of pp rows
|
||||
pp_index += 2
|
||||
|
||||
# Final addition of remaining bits using chosen unsigned multi bit adder
|
||||
# Obtain proper adder name with its bit width (columns bit pairs minus the first alone bit)
|
||||
adder_name = unsigned_adder_class_name(a=a, b=b).prefix + str(self.rows[0].N)
|
||||
adder_a = Bus(prefix="a", N=self.rows[0].N)
|
||||
adder_b = Bus(prefix="b", N=self.rows[1].N)
|
||||
[adder_a.connect(w, self.rows[0].get_wire(w)) for w in range(0, self.rows[0].N)]
|
||||
[adder_b.connect(w, self.rows[1].get_wire(w)) for w in range(0, self.rows[1].N)]
|
||||
final_adder = unsigned_adder_class_name(a=adder_a, b=adder_b, prefix=self.prefix, name=adder_name, inner_component=True)
|
||||
self.add_component(final_adder)
|
||||
[self.out.connect(o, final_adder.out.get_wire(o), inserted_wire_desired_index=o) for o in range(0, final_adder.out.N-1)]
|
||||
|
||||
|
||||
class SignedWallaceCSAMultiplier(MultiplierCircuit):
|
||||
"""Class representing signed wallace multiplier composed of carry save adder components.
|
||||
|
||||
Signed wallace multiplier represents fast N-bit multiplier which utilizes
|
||||
the functionality of wallace tree reduction algorithm proposed by Chris Wallace and uses Baugh-Wooley algorithm
|
||||
to perform signed multiplication.
|
||||
|
||||
First partial products are calculated for each bit pair that form the partial product multiplication rows.
|
||||
This implementation uses carry save adder components to efficiently implement reduction of partial products utilizing the parallelism of the carry save adders.
|
||||
At last the reduced pairs are inserted into chosen multi bit unsigned adder to execute their summation and obtain the final output bits, additional XOR gate serve the necessary sign extension.
|
||||
|
||||
Wallace tree algorithm is described more in detail here:
|
||||
https://en.wikipedia.org/wiki/Wallace_tree
|
||||
|
||||
It presents a faster version of multiplier opposed to the conventional architectures that are composed of interconnected half/full adders.
|
||||
|
||||
Description of the __init__ method.
|
||||
|
||||
Args:
|
||||
a (Bus): First input bus.
|
||||
b (Bus): Second input bus.
|
||||
prefix (str, optional): Prefix name of signed csa wallace multiplier. Defaults to "".
|
||||
name (str, optional): Name of signed csa wallace multiplier. Defaults to "s_wallaceCSA_cla".
|
||||
unsigned_adder_class_name (str, optional): Unsigned multi bit adder used to obtain final sums of products. Defaults to UnsignedCarryLookaheadAdder.
|
||||
"""
|
||||
def __init__(self, a: Bus, b: Bus, prefix: str = "", name: str = "s_wallaceCSA_cla", unsigned_adder_class_name: str = UnsignedCarryLookaheadAdder, **kwargs):
|
||||
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)
|
||||
|
||||
# Initialize all rows partial products forming AND gates matrix
|
||||
self.rows = self.init_row_lengths()
|
||||
|
||||
# Zero extension of partial product rows
|
||||
for i in range(0, len(self.rows)):
|
||||
self.rows[i] = Bus(prefix=self.rows[i].prefix, wires_list=[ConstantWireValue0() for _ in range(0, i)] + self.rows[i].bus)
|
||||
|
||||
while len(self.rows) > 2:
|
||||
# Gradual creation of signed csa adder components to reduce the pp rows to the total count of 2
|
||||
pp_index = 0
|
||||
while pp_index < len(self.rows) and (pp_index+2) < len(self.rows):
|
||||
csa_reduction = CarrySaveAdderComponent(a=self.rows[pp_index], b=self.rows[pp_index+1], c=self.rows[pp_index+2], prefix=self.prefix+"_csa"+str(self.get_instance_num(cls=CarrySaveAdderComponent)), inner_component=True, signed=True)
|
||||
self.add_component(csa_reduction)
|
||||
|
||||
# 3 pp rows have been reduced to 2
|
||||
[self.rows.pop(pp_index) for i in range(3)]
|
||||
|
||||
# Append rows of sum and carry results from csa calculation
|
||||
csa_sums_N = self.out.N if csa_reduction.sum_bits.N > self.out.N-1 else csa_reduction.sum_bits.N
|
||||
csa_sums = Bus(prefix=self.prefix+"_csa_s"+str(self.get_instance_num(cls=CarrySaveAdderComponent)), N=csa_sums_N)
|
||||
csa_sums.connect_bus(connecting_bus=csa_reduction.out, end_connection_pos=csa_sums_N)
|
||||
|
||||
csa_carries_N = self.out.N if csa_reduction.carry_bits.N > self.out.N-1 else csa_reduction.carry_bits.N
|
||||
csa_carries = Bus(prefix=self.prefix+"_csa_c"+str(self.get_instance_num(cls=CarrySaveAdderComponent)), N=csa_carries_N)
|
||||
csa_carries.connect_bus(connecting_bus=csa_reduction.out, start_connection_pos=int(csa_reduction.out.N/2), end_connection_pos=int(csa_reduction.out.N/2)+csa_carries.N, offset=int(csa_reduction.out.N/2))
|
||||
|
||||
self.rows.insert(pp_index, csa_carries)
|
||||
self.rows.insert(pp_index, csa_sums)
|
||||
|
||||
# Update of the number of pp rows
|
||||
pp_index += 2
|
||||
|
||||
# Final addition of remaining bits using chosen unsigned multi bit adder
|
||||
# Obtain proper adder name with its bit width (columns bit pairs minus the first alone bit)
|
||||
adder_name = unsigned_adder_class_name(a=a, b=b).prefix + str(self.rows[0].N)
|
||||
adder_a = Bus(prefix="a", N=self.rows[0].N)
|
||||
adder_b = Bus(prefix="b", N=self.rows[1].N)
|
||||
[adder_a.connect(w, self.rows[0].get_wire(w)) for w in range(0, self.rows[0].N)]
|
||||
[adder_b.connect(w, self.rows[1].get_wire(w)) for w in range(0, self.rows[1].N)]
|
||||
final_adder = unsigned_adder_class_name(a=adder_a, b=adder_b, prefix=self.prefix, name=adder_name, inner_component=True)
|
||||
self.add_component(final_adder)
|
||||
[self.out.connect(o, final_adder.out.get_wire(o), inserted_wire_desired_index=o) for o in range(0, final_adder.out.N-1)]
|
||||
|
||||
# Final XOR to ensure proper sign extension
|
||||
obj_xor = XorGate(ConstantWireValue1(), self.out.get_wire(self.out.N-1), prefix=self.prefix+"_xor"+str(self.get_instance_num(cls=XorGate)), parent_component=self)
|
||||
self.add_component(obj_xor)
|
||||
self.out.connect(self.out.N-1, obj_xor.out)
|
@ -156,7 +156,7 @@ class SignedWallaceMultiplier(MultiplierCircuit):
|
||||
self.b.bus_extend(N=self.N, prefix=b.prefix)
|
||||
|
||||
# Initialize all columns partial products forming AND/NAND gates matrix based on Baugh-Wooley multiplication
|
||||
self.columns = self.init_column_heights(signed=True)
|
||||
self.columns = self.init_column_heights()
|
||||
|
||||
# Not used for 1 bit multiplier
|
||||
if self.N != 1:
|
||||
|
@ -1,4 +1,4 @@
|
||||
from .wires import Wire
|
||||
from .wires import Wire, ConstantWireValue0, ConstantWireValue1
|
||||
|
||||
|
||||
class Bus():
|
||||
@ -24,7 +24,7 @@ class Bus():
|
||||
self.prefix = prefix
|
||||
self.bus = wires_list
|
||||
self.N = len(self.bus)
|
||||
|
||||
|
||||
# Determine C code signedness
|
||||
self.signed = signed
|
||||
if self.N > 8:
|
||||
@ -48,23 +48,23 @@ class Bus():
|
||||
"""
|
||||
return self.out_bus
|
||||
|
||||
def bus_extend(self, N: int, prefix: str = "bus", last_wire_extend: bool = True):
|
||||
def bus_extend(self, N: int, prefix: str = "bus", desired_extension_wire: Wire = ConstantWireValue0()):
|
||||
"""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.
|
||||
desired_extension_wire (Wire, optional): Specifies the wire that should be connected to all of the extending bus wires. Defaults to ConstantWireValue0().
|
||||
"""
|
||||
# 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))
|
||||
|
||||
for w_index in range(self.N, N):
|
||||
self.connect(bus_wire_index=w_index, inner_component_out_wire=desired_extension_wire)
|
||||
|
||||
self.N = N
|
||||
|
||||
|
||||
def get_wire(self, wire_index: int = 0):
|
||||
"""Retrieves a wire from the bus by a given index.
|
||||
@ -100,7 +100,7 @@ class Bus():
|
||||
elif inserted_wire_desired_index != -1:
|
||||
self.bus[bus_wire_index] = Wire(name=inner_component_out_wire.name, prefix=inner_component_out_wire.parent_bus.prefix, index=inserted_wire_index, value=inner_component_out_wire.value, parent_bus=self)
|
||||
|
||||
def connect_bus(self, connecting_bus: object, start_connection_pos: int = 0, end_connection_pos: int = -1):
|
||||
def connect_bus(self, connecting_bus: object, start_connection_pos: int = 0, end_connection_pos: int = -1, offset: int = 0):
|
||||
"""Ensures connection of specified bus wires to this bus wires.
|
||||
|
||||
Used for connection of some inner circuit component's output bus (`connecting_bus`) wires
|
||||
@ -110,11 +110,12 @@ class Bus():
|
||||
connecting_bus (object): Specifies the connecting bus.
|
||||
start_connection_pos (int, optional): Specifies the position from which to start interconnecting wires from the `connecting_bus` to this `self` bus. Defaults to 0.
|
||||
end_connection_pos (int, optional): Specifies the position from which to end interconnecting wires from the `connecting_bus` to this `self` bus. Defaults to -1.
|
||||
offset (int, optional): Specifies the offset wire index position in the `self` bus for proper connection (i.e. wire at index position 5 in the `connecting_bus` with offset set to 5 will be connected to `self` bus index position 0). Default to 0.
|
||||
"""
|
||||
if end_connection_pos == -1:
|
||||
end_connection_pos = self.N
|
||||
|
||||
[self.connect(o, connecting_bus.get_wire(o), inserted_wire_desired_index=o) for o in range(start_connection_pos, end_connection_pos)]
|
||||
[self.connect(o-offset, connecting_bus.get_wire(o), inserted_wire_desired_index=o) for o in range(start_connection_pos, end_connection_pos)]
|
||||
|
||||
""" PYTHON CODE GENERATION """
|
||||
def return_bus_wires_values_python_flat(self):
|
||||
@ -123,8 +124,11 @@ class Bus():
|
||||
Returns:
|
||||
str: Python code for assigning wire values into bus represented in Python code variable.
|
||||
"""
|
||||
return "".join([f" {self.prefix} = 0\n"] + [f" {self.prefix} |= {w.return_wire_value_python_flat(offset=self.bus.index(w))}" for w in self.bus])
|
||||
|
||||
# Ensures correct binding between the bus wire index and the wire itself
|
||||
# It is used for the case when multiple of the same wire (e.g. `ContantWireValue0()`) are present in the bus (its id would otherwise be incorrect when using `self.bus.index(_)`)
|
||||
mapped_positions = [(w_id, self.bus[w_id]) for w_id in range(self.N)]
|
||||
return "".join([f" {self.prefix} = 0\n"] + [f" {self.prefix} |= {w[1].return_wire_value_python_flat(offset=w[0])}" for w in mapped_positions])
|
||||
|
||||
def return_bus_wires_sign_extend_python(self):
|
||||
"""Sign extends the bus's corresponding Python variable (object) to ensure proper Python code variable signedness.
|
||||
|
||||
@ -145,7 +149,6 @@ class Bus():
|
||||
str: C code for declaration and initialization of bus name.
|
||||
"""
|
||||
return f" {self.c_type} {self.prefix} = 0;\n"
|
||||
|
||||
|
||||
def return_bus_wires_values_c_flat(self):
|
||||
"""Retrieves values from bus's wires and stores them in bus's corresponding C variable at proper offset bit position in the bus for flat generation.
|
||||
@ -153,7 +156,10 @@ class Bus():
|
||||
Returns:
|
||||
str: C code for assigning wire values into bus represented in C code variable.
|
||||
"""
|
||||
return "".join([f" {self.prefix} |= {w.return_wire_value_c_flat(offset=self.bus.index(w))}" for w in self.bus])
|
||||
# Ensures correct binding between the bus wire index and the wire itself
|
||||
# It is used for the case when multiple of the same wire (e.g. `ContantWireValue0()`) are present in the bus (its id would otherwise be incorrect when using `self.bus.index(_)`)
|
||||
mapped_positions = [(w_id, self.bus[w_id]) for w_id in range(self.N)]
|
||||
return "".join([f" {self.prefix} |= {w[1].return_wire_value_c_flat(offset=w[0])}" for w in mapped_positions])
|
||||
|
||||
def return_bus_wires_values_c_hier(self):
|
||||
"""Retrieves values from bus's wires and stores them in bus's corresponding C variable at proper offset bit position in the bus for hierarchical generation.
|
||||
@ -161,7 +167,10 @@ class Bus():
|
||||
Returns:
|
||||
str: C code for assigning wire values into bus represented in C code variable.
|
||||
"""
|
||||
return "".join([f" {self.prefix} |= {w.return_wire_value_c_hier(offset=self.bus.index(w))}" for w in self.bus])
|
||||
# Ensures correct binding between the bus wire index and the wire itself
|
||||
# It is used for the case when multiple of the same wire (e.g. `ContantWireValue0()`) are present in the bus (its id would otherwise be incorrect when using `self.bus.index(_)`)
|
||||
mapped_positions = [(w_id, self.bus[w_id]) for w_id in range(self.N)]
|
||||
return "".join([f" {self.prefix} |= {w[1].return_wire_value_c_hier(offset=w[0])}" for w in mapped_positions])
|
||||
|
||||
def return_bus_wires_sign_extend_c(self):
|
||||
"""Sign extends the bus's corresponding C variable to ensure proper C code variable signedness.
|
||||
@ -182,7 +191,10 @@ class Bus():
|
||||
Returns:
|
||||
str: Verilog code for assigning wire values into bus represented in Verilog code bus variable.
|
||||
"""
|
||||
return "".join([f" assign {self.prefix}[{self.bus.index(w)}] = {w.return_wire_value_v_flat()}" for w in self.bus])
|
||||
# Ensures correct binding between the bus wire index and the wire itself
|
||||
# It is used for the case when multiple of the same wire (e.g. `ContantWireValue0()`) are present in the bus (its id would otherwise be incorrect when using `self.bus.index(_)`)
|
||||
mapped_positions = [(w_id, self.bus[w_id]) for w_id in range(self.N)]
|
||||
return "".join([f" assign {self.prefix}[{w[0]}] = {w[1].return_wire_value_v_flat()}" for w in mapped_positions])
|
||||
|
||||
def return_bus_wires_values_v_hier(self):
|
||||
"""Retrieves values from bus's wires and stores them in bus's corresponding Verilog variable at proper offset bit position in the bus for hierarchical generation.
|
||||
@ -190,7 +202,10 @@ class Bus():
|
||||
Returns:
|
||||
str: Verilog code for assigning wire values into bus represented in Verilog code variable.
|
||||
"""
|
||||
return "".join([f" assign {self.prefix}[{self.bus.index(w)}] = {w.return_wire_value_v_hier()}" for w in self.bus])
|
||||
# Ensures correct binding between the bus wire index and the wire itself
|
||||
# It is used for the case when multiple of the same wire (e.g. `ContantWireValue0()`) are present in the bus (its id would otherwise be incorrect when using `self.bus.index(_)`)
|
||||
mapped_positions = [(w_id, self.bus[w_id]) for w_id in range(self.N)]
|
||||
return "".join([f" assign {self.prefix}[{w[0]}] = {w[1].return_wire_value_v_hier()}" for w in mapped_positions])
|
||||
|
||||
def get_unique_assign_out_wires_v(self, circuit_block: object):
|
||||
"""Returns bus's wires used for hierarchical one bit subcomponent's function block invocation and output wires assignments.
|
||||
@ -215,7 +230,10 @@ class Bus():
|
||||
Returns:
|
||||
str: Blif code for declaration of individual bus wires.
|
||||
"""
|
||||
return "".join([f" {w.get_declaration_blif(prefix=self.prefix, offset=self.bus.index(w), array=array)}" for w in self.bus])
|
||||
# Ensures correct binding between the bus wire index and the wire itself
|
||||
# It is used for the case when multiple of the same wire (e.g. `ContantWireValue0()`) are present in the bus (its id would otherwise be incorrect when using `self.bus.index(_)`)
|
||||
mapped_positions = [(w_id, self.bus[w_id]) for w_id in range(self.N)]
|
||||
return "".join([f" {w[1].get_declaration_blif(prefix=self.prefix, offset=w[0], array=array)}" for w in mapped_positions])
|
||||
|
||||
def get_wire_assign_blif(self, output: bool = False):
|
||||
"""Assign all bits from the bus as each individual wires or assign wires into the corresponding output bus position in Blif code representation.
|
||||
@ -226,7 +244,10 @@ class Bus():
|
||||
Returns:
|
||||
str: Blif code for bus wires assignments.
|
||||
"""
|
||||
return "".join([w.get_assign_blif(prefix=self.prefix+f"[{self.bus.index(w)}]", output=output) for w in self.bus])
|
||||
# Ensures correct binding between the bus wire index and the wire itself
|
||||
# It is used for the case when multiple of the same wire (e.g. `ContantWireValue0()`) are present in the bus (its id would otherwise be incorrect when using `self.bus.index(_)`)
|
||||
mapped_positions = [(w_id, self.bus[w_id]) for w_id in range(self.N)]
|
||||
return "".join([w[1].get_assign_blif(prefix=self.prefix+f"[{w[0]}]", output=output) for w in mapped_positions])
|
||||
|
||||
def get_unique_assign_out_wires_blif(self, function_block_out_bus: object):
|
||||
"""Assigns unique output wires to their respective outputs of subcomponent's function block modul in hierarchical Blif subcomponent's invocation.
|
||||
|
Loading…
x
Reference in New Issue
Block a user