From 6f05db002e65c1969c1a166c80666d9b521012cb Mon Sep 17 00:00:00 2001 From: Honza Date: Fri, 18 Feb 2022 17:00:31 +0100 Subject: [PATCH] Implemented CSA and Wallace tree multiplier composing of CSAs. Also did some code cleanup. --- .../core/arithmetic_circuits/__init__.py | 3 +- .../arithmetic_circuits/arithmetic_circuit.py | 193 +++++++++++++++- .../arithmetic_circuits/general_circuit.py | 32 ++- .../arithmetic_circuits/multiplier_circuit.py | 65 +++++- .../multi_bit_circuits/adders/__init__.py | 5 + .../adders/carry_save_adder.py | 155 +++++++++++++ ariths_gen/multi_bit_circuits/cgp_circuit.py | 47 ++-- .../multipliers/__init__.py | 5 + .../multipliers/dadda_multiplier.py | 2 +- .../multipliers/wallace_csa_multiplier.py | 214 ++++++++++++++++++ .../multipliers/wallace_multiplier.py | 2 +- ariths_gen/wire_components/buses.py | 59 +++-- 12 files changed, 698 insertions(+), 84 deletions(-) create mode 100644 ariths_gen/multi_bit_circuits/adders/carry_save_adder.py create mode 100644 ariths_gen/multi_bit_circuits/multipliers/wallace_csa_multiplier.py diff --git a/ariths_gen/core/arithmetic_circuits/__init__.py b/ariths_gen/core/arithmetic_circuits/__init__.py index 9c792a3..cf0a676 100644 --- a/ariths_gen/core/arithmetic_circuits/__init__.py +++ b/ariths_gen/core/arithmetic_circuits/__init__.py @@ -1,5 +1,6 @@ from .arithmetic_circuit import ( - ArithmeticCircuit + ArithmeticCircuit, + ThreeInputArithmeticCircuit ) from .general_circuit import (GeneralCircuit ) diff --git a/ariths_gen/core/arithmetic_circuits/arithmetic_circuit.py b/ariths_gen/core/arithmetic_circuits/arithmetic_circuit.py index ef905db..e20699d 100644 --- a/ariths_gen/core/arithmetic_circuits/arithmetic_circuit.py +++ b/ariths_gen/core/arithmetic_circuits/arithmetic_circuit.py @@ -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()}" diff --git a/ariths_gen/core/arithmetic_circuits/general_circuit.py b/ariths_gen/core/arithmetic_circuits/general_circuit.py index 047ed7d..a6d7d73 100644 --- a/ariths_gen/core/arithmetic_circuits/general_circuit.py +++ b/ariths_gen/core/arithmetic_circuits/general_circuit.py @@ -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. diff --git a/ariths_gen/core/arithmetic_circuits/multiplier_circuit.py b/ariths_gen/core/arithmetic_circuits/multiplier_circuit.py index 59971c4..28c3e98 100644 --- a/ariths_gen/core/arithmetic_circuits/multiplier_circuit.py +++ b/ariths_gen/core/arithmetic_circuits/multiplier_circuit.py @@ -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: diff --git a/ariths_gen/multi_bit_circuits/adders/__init__.py b/ariths_gen/multi_bit_circuits/adders/__init__.py index 54cb252..2a860dc 100644 --- a/ariths_gen/multi_bit_circuits/adders/__init__.py +++ b/ariths_gen/multi_bit_circuits/adders/__init__.py @@ -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 +) diff --git a/ariths_gen/multi_bit_circuits/adders/carry_save_adder.py b/ariths_gen/multi_bit_circuits/adders/carry_save_adder.py new file mode 100644 index 0000000..a01270b --- /dev/null +++ b/ariths_gen/multi_bit_circuits/adders/carry_save_adder.py @@ -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) diff --git a/ariths_gen/multi_bit_circuits/cgp_circuit.py b/ariths_gen/multi_bit_circuits/cgp_circuit.py index 2334adc..9fc444f 100644 --- a/ariths_gen/multi_bit_circuits/cgp_circuit.py +++ b/ariths_gen/multi_bit_circuits/cgp_circuit.py @@ -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" \ No newline at end of file + self.c_data_type = "int64_t" diff --git a/ariths_gen/multi_bit_circuits/multipliers/__init__.py b/ariths_gen/multi_bit_circuits/multipliers/__init__.py index a52c50a..777e0a3 100644 --- a/ariths_gen/multi_bit_circuits/multipliers/__init__.py +++ b/ariths_gen/multi_bit_circuits/multipliers/__init__.py @@ -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 +) diff --git a/ariths_gen/multi_bit_circuits/multipliers/dadda_multiplier.py b/ariths_gen/multi_bit_circuits/multipliers/dadda_multiplier.py index 9c47be4..7acfb1d 100644 --- a/ariths_gen/multi_bit_circuits/multipliers/dadda_multiplier.py +++ b/ariths_gen/multi_bit_circuits/multipliers/dadda_multiplier.py @@ -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: diff --git a/ariths_gen/multi_bit_circuits/multipliers/wallace_csa_multiplier.py b/ariths_gen/multi_bit_circuits/multipliers/wallace_csa_multiplier.py new file mode 100644 index 0000000..64c1933 --- /dev/null +++ b/ariths_gen/multi_bit_circuits/multipliers/wallace_csa_multiplier.py @@ -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) diff --git a/ariths_gen/multi_bit_circuits/multipliers/wallace_multiplier.py b/ariths_gen/multi_bit_circuits/multipliers/wallace_multiplier.py index 4098cdf..868dd77 100644 --- a/ariths_gen/multi_bit_circuits/multipliers/wallace_multiplier.py +++ b/ariths_gen/multi_bit_circuits/multipliers/wallace_multiplier.py @@ -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: diff --git a/ariths_gen/wire_components/buses.py b/ariths_gen/wire_components/buses.py index ed6d040..4e1c99e 100644 --- a/ariths_gen/wire_components/buses.py +++ b/ariths_gen/wire_components/buses.py @@ -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.