def __init__(self,
                 a: Bus,
                 b: Bus,
                 cla_block_size: int = 4,
                 prefix: str = "s_cla"):
        super().__init__(a=a,
                         b=b,
                         cla_block_size=cla_block_size,
                         prefix=prefix)
        self.c_data_type = "int64_t"

        # Additional XOR gates to ensure correct sign extension in case of sign addition
        sign_xor_1 = XorGate(self.a.get_wire(self.N - 1),
                             self.b.get_wire(self.N - 1),
                             prefix=self.prefix + "_xor" +
                             str(self.get_instance_num(cls=XorGate)),
                             parent_component=self)
        self.add_component(sign_xor_1)
        sign_xor_2 = XorGate(sign_xor_1.out,
                             self.get_previous_component(2).out,
                             prefix=self.prefix + "_xor" +
                             str(self.get_instance_num(cls=XorGate)),
                             parent_component=self)
        self.add_component(sign_xor_2)
        self.out.connect(self.N, sign_xor_2.out)
Exemplo n.º 2
0
    def __init__(self,
                 a: Wire = Wire(name="a"),
                 b: Wire = Wire(name="b"),
                 c: Wire = Wire(name="cin"),
                 prefix: str = "fa"):
        super().__init__(a, b, c, prefix)
        # 2 wires for component's bus output (sum, cout)
        self.out = Bus(self.prefix + "_out", 2)

        # PG logic
        propagate_xor = XorGate(a,
                                b,
                                prefix=self.prefix + "_xor" +
                                str(self.get_instance_num(cls=XorGate)),
                                parent_component=self)
        self.add_component(propagate_xor)
        generate_and = AndGate(a,
                               b,
                               prefix=self.prefix + "_and" +
                               str(self.get_instance_num(cls=AndGate)),
                               parent_component=self)
        self.add_component(generate_and)

        # Sum
        # XOR gate for calculation of 1-bit sum
        obj_xor = XorGate(propagate_xor.out,
                          c,
                          prefix=self.prefix + "_xor" +
                          str(self.get_instance_num(cls=XorGate)),
                          outid=0,
                          parent_component=self)
        self.add_component(obj_xor)
        self.out.connect(0, obj_xor.out)

        # Cout
        # AND gate for calculation of 1-bit cout
        obj_and = AndGate(propagate_xor.out,
                          c,
                          prefix=self.prefix + "_and" +
                          str(self.get_instance_num(cls=AndGate)),
                          parent_component=self)
        self.add_component(obj_and)

        obj_or = OrGate(generate_and.out,
                        obj_and.out,
                        prefix=self.prefix + "_or" +
                        str(self.get_instance_num(cls=OrGate)),
                        outid=1,
                        parent_component=self)
        self.add_component(obj_or)

        self.out.connect(1, obj_or.out)
    def __init__(self,
                 a: Wire = Wire(name="a"),
                 b: Wire = Wire(name="b"),
                 prefix: str = "pg_logic"):
        super().__init__(a, b, prefix)
        # 3 wires for component's bus output (propagate, generate, sum)
        self.out = Bus(self.prefix + "_out", 3)

        # PG logic
        propagate_or = OrGate(a,
                              b,
                              prefix=self.prefix + "_or" +
                              str(self.get_instance_num(cls=OrGate)),
                              outid=0,
                              parent_component=self)
        self.add_component(propagate_or)
        generate_and = AndGate(a,
                               b,
                               prefix=self.prefix + "_and" +
                               str(self.get_instance_num(cls=AndGate)),
                               outid=1,
                               parent_component=self)
        self.add_component(generate_and)
        sum_xor = XorGate(a,
                          b,
                          prefix=self.prefix + "_xor" +
                          str(self.get_instance_num(cls=XorGate)),
                          outid=2,
                          parent_component=self)
        self.add_component(sum_xor)

        self.out.connect(0, propagate_or.out)
        self.out.connect(1, generate_and.out)
        self.out.connect(2, sum_xor.out)
    def __init__(self,
                 a: Wire = Wire(name="a"),
                 b: Wire = Wire(name="b"),
                 prefix: str = "hs"):
        super().__init__(a, b, prefix)
        # 2 wires for component's bus output (difference, bout)
        self.out = Bus(self.prefix + "_out", 2)

        # Difference
        # XOR gate for calculation of 1-bit difference
        difference_xor = XorGate(a=self.a,
                                 b=self.b,
                                 prefix=self.prefix + "_xor" +
                                 str(self.get_instance_num(cls=XorGate)),
                                 outid=0,
                                 parent_component=self)
        self.add_component(difference_xor)
        self.out.connect(0, difference_xor.out)

        # Bout
        # NOT and AND gates for calculation of 1-bit borrow out
        not_obj = NotGate(a=self.a,
                          prefix=self.prefix + "_not" +
                          str(self.get_instance_num(cls=NotGate)),
                          parent_component=self)
        self.add_component(not_obj)

        borrow_and = AndGate(a=not_obj.out,
                             b=self.b,
                             prefix=self.prefix + "_xor" +
                             str(self.get_instance_num(cls=XorGate)),
                             outid=1,
                             parent_component=self)
        self.add_component(borrow_and)
        self.out.connect(1, borrow_and.out)
    def __init__(self, a: Bus, b: Bus, bypass_block_size: int = 4, prefix: str = "u_cska"):
        super().__init__()
        self.N = max(a.N, b.N)
        self.prefix = prefix
        self.a = Bus(prefix=a.prefix, wires_list=a.bus)
        self.b = Bus(prefix=b.prefix, wires_list=b.bus)

        # 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)

        # Output wires for N sum bits and additional cout bit
        self.out = Bus(self.prefix+"_out", self.N+1)

        # To signify current number of blocks and number of bits that remain to be added into function blocks
        N_blocks = 0
        N_wires = self.N
        cin = ConstantWireValue0()

        while N_wires != 0:
            propagate_wires = []
            block_size = bypass_block_size if N_wires >= bypass_block_size else N_wires

            for i in range(block_size):
                # Generate propagate wires for corresponding bit pairs
                propagate_xor = XorGate(a=self.a.get_wire((N_blocks*bypass_block_size)+i), b=self.b.get_wire((N_blocks*bypass_block_size)+i), prefix=self.prefix+"_xor"+str(self.get_instance_num(cls=XorGate)), parent_component=self)
                self.add_component(propagate_xor)
                propagate_wires.append(propagate_xor.out)

                if N_blocks == 0 and i == 0:
                    obj_adder = HalfAdder(a=self.a.get_wire((N_blocks*bypass_block_size)+i), b=self.b.get_wire((N_blocks*bypass_block_size)+i), prefix=self.prefix+"_ha"+str(self.get_instance_num(cls=HalfAdder)))
                else:
                    obj_adder = FullAdder(a=self.a.get_wire((N_blocks*bypass_block_size)+i), b=self.b.get_wire((N_blocks*bypass_block_size)+i), c=cout, prefix=self.prefix+"_fa"+str(self.get_instance_num(cls=FullAdder)))

                cout = obj_adder.get_carry_wire()
                self.add_component(obj_adder)
                # Connecting adder's output sum bit to its proper position within the described circuit's output bus
                self.out.connect(i+(N_blocks*bypass_block_size), obj_adder.get_sum_wire())

            # ANDing of propagate wires, gate's output serves as select signal into 2:1 multiplexer and signifies whether block's input carry should be propagated (thus reducing delay) or not
            propagation_and = MultipleInputLogicGate(a=Bus(prefix=self.prefix+f"_propagate_signal{N_blocks}", N=len(propagate_wires), wires_list=propagate_wires), two_input_gate_cls=AndGate, parent_component=self, prefix=self.prefix+f"_and_propagate{N_blocks}")

            mux = TwoOneMultiplexer(a=cout, b=cin, c=propagation_and.out, prefix=self.prefix+"_mux2to1"+str(self.get_instance_num(cls=TwoOneMultiplexer)))
            self.add_component(mux)

            # Updating cin for the the next bypass block
            # Also updating cout value which is used as cin for the first adder of the next block
            cin = mux.out.get_wire()
            cout = mux.out.get_wire()
            N_wires -= block_size
            N_blocks += 1

        # Connection of final Cout
        self.out.connect(self.N, cin)
Exemplo n.º 6
0
    def __init__(self,
                 a: Wire = Wire(name="d0"),
                 b: Wire = Wire(name="d1"),
                 c: Wire = Wire(name="sel"),
                 prefix: str = "mux2to1"):
        super().__init__(a, b, c, prefix)
        # Represents select signal (self.c naming for proper unified generation)
        self.c = c
        # 1 wire for component's output bus
        self.out = Bus(self.prefix + "_out", 1)

        # 2:1MUX logic
        and_obj = AndGate(a=self.b,
                          b=self.c,
                          prefix=self.prefix + "_and" +
                          str(self.get_instance_num(cls=AndGate)),
                          parent_component=self)
        self.add_component(and_obj)

        not_obj = NotGate(a=self.c,
                          prefix=self.prefix + "_not" +
                          str(self.get_instance_num(cls=NotGate)),
                          parent_component=self)
        self.add_component(not_obj)

        and_obj = AndGate(a=self.a,
                          b=self.get_previous_component().out,
                          prefix=self.prefix + "_and" +
                          str(self.get_instance_num(cls=AndGate)),
                          parent_component=self)
        self.add_component(and_obj)

        xor_obj = XorGate(a=self.get_previous_component(3).out,
                          b=self.get_previous_component().out,
                          prefix=self.prefix + "_xor" +
                          str(self.get_instance_num(cls=XorGate)),
                          parent_component=self)
        self.add_component(xor_obj)

        # Connection of MUX output wire
        self.out.connect(0, xor_obj.out)
    def __init__(self,
                 a: Bus,
                 b: Bus,
                 cla_block_size: int = 4,
                 prefix: str = "u_cla"):
        super().__init__()
        self.N = max(a.N, b.N)
        self.prefix = prefix
        self.a = Bus(prefix=a.prefix, wires_list=a.bus)
        self.b = Bus(prefix=b.prefix, wires_list=b.bus)

        # 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)

        # Output wires for N sum bits and additional cout bit
        self.out = Bus(self.prefix + "_out", self.N + 1)

        # To signify current number of blocks and number of bits that remain to be added into function blocks
        N_blocks = 0
        N_wires = self.N
        cin = ConstantWireValue0()

        while N_wires != 0:
            # Lists containing all propagate/generate wires
            self.propagate = []
            self.generate = []
            # Cin0 used as a first generate wire for obtaining next carry bits
            self.generate.append(cin)
            block_size = cla_block_size if N_wires >= cla_block_size else N_wires

            # Gradual addition of propagate/generate logic blocks and AND/OR gates for Cout bits generation, XOR gates for Sum bits generation
            for i in range(block_size):
                pg_block = PGLogicBlock(
                    self.a.get_wire((N_blocks * cla_block_size) + i),
                    self.b.get_wire((N_blocks * cla_block_size) + i),
                    prefix=self.prefix + "_pg_logic" +
                    str(self.get_instance_num(cls=PGLogicBlock)))
                self.propagate.append(pg_block.get_propagate_wire())
                self.generate.append(pg_block.get_generate_wire())
                self.add_component(pg_block)

                if i == 0 and N_blocks == 0:
                    obj_sum_xor = XorGate(
                        pg_block.get_sum_wire(),
                        cin,
                        prefix=self.prefix + "_xor" +
                        str(self.get_instance_num(cls=XorGate)),
                        parent_component=self)
                    self.add_component(obj_sum_xor)
                    self.out.connect(i + (N_blocks * cla_block_size),
                                     obj_sum_xor.out)

                    # Carry propagation calculation
                    obj_and = AndGate(
                        self.propagate[(N_blocks * cla_block_size) + i],
                        self.generate[(N_blocks * cla_block_size) + i],
                        prefix=self.prefix + "_and" +
                        str(self.get_instance_num(cls=AndGate)),
                        parent_component=self)
                    self.add_component(obj_and)

                    # Carry bit generation
                    obj_cout_or = OrGate(
                        pg_block.get_generate_wire(),
                        self.get_previous_component().out,
                        prefix=self.prefix + "_or" + str(
                            self.get_instance_num(cls=OrGate,
                                                  count_disabled_gates=False)),
                        parent_component=self)
                    self.add_component(obj_cout_or)
                else:
                    obj_sum_xor = XorGate(
                        pg_block.get_sum_wire(),
                        self.get_previous_component(2).out,
                        prefix=self.prefix + "_xor" +
                        str(self.get_instance_num(cls=XorGate)),
                        parent_component=self)
                    self.add_component(obj_sum_xor)
                    self.out.connect(i + (N_blocks * cla_block_size),
                                     obj_sum_xor.out)

                    # List of AND gates outputs that are later combined in a multi-bit OR gate
                    composite_or_gates_inputs = []

                    for g_index in range(len(self.generate) - 1):
                        composite_wires = []
                        # Getting a list of wires used for current bit position cout composite AND gate's generation
                        # E.g. for Cout2 = G1 + G0·P1 C0·P0·P1 it gets a list containing [C0,P0,P1] then [G0,P1]
                        composite_wires.append(self.generate[g_index])
                        for p_index in range(
                                len(self.propagate) - 1, g_index - 1, -1):
                            composite_wires.append(self.propagate[p_index])

                        # For each pg pair values algorithmically combine two input AND gates to replace multiple input gates (resolves fan-in issue)
                        pg_wires = Bus(wires_list=composite_wires)
                        multi_bit_and_gate = MultipleInputLogicGate(
                            a=pg_wires,
                            two_input_gate_cls=AndGate,
                            prefix=self.prefix + "_and",
                            parent_component=self)
                        composite_or_gates_inputs.append(
                            multi_bit_and_gate.out)

                    # Final OR gates cascade using generated AND gates output wires
                    composite_or_wires = Bus(
                        wires_list=composite_or_gates_inputs)
                    multi_bit_or_gate = MultipleInputLogicGate(
                        a=composite_or_wires,
                        two_input_gate_cls=OrGate,
                        prefix=self.prefix + "_or",
                        parent_component=self)

                    # Carry bit generation
                    obj_cout_or = OrGate(
                        pg_block.get_generate_wire(),
                        multi_bit_or_gate.out,
                        prefix=self.prefix + "_or" + str(
                            self.get_instance_num(cls=OrGate,
                                                  count_disabled_gates=False)),
                        parent_component=self)
                    self.add_component(obj_cout_or)

            # Updating cin for the the next bypass block
            # Also updating cout value which is used as cin for the first adder of the next block
            cin = obj_cout_or.out

            N_wires -= block_size
            N_blocks += 1

        # Connection of final Cout
        self.out.connect(self.N, cin)
Exemplo n.º 8
0
    def __init__(self,
                 a: Bus,
                 b: Bus,
                 prefix: str = "s_wallace_cla",
                 unsigned_adder_class_name: str = UnsignedCarryLookaheadAdder):
        super().__init__()
        self.N = max(a.N, b.N)
        self.prefix = prefix
        self.a = Bus(prefix=a.prefix, wires_list=a.bus)
        self.b = Bus(prefix=b.prefix, wires_list=b.bus)

        # 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)

        # Output wires for multiplication product
        self.out = Bus(self.prefix + "_out", self.N * 2)

        # Initialize all columns partial products forming AND/NAND gates matrix based on Baugh-Wooley multiplication
        self.columns = self.init_column_heights(signed=True)

        # Not used for 1 bit multiplier
        if self.N != 1:
            # Adding constant wire with value 1 to achieve signedness based on Baugh-Wooley multiplication algorithm
            # (adding constant value bit to last column (with one bit) to combine them in XOR gate to get the correct final multplication output bit at the end)
            self.columns[self.N].insert(1, ConstantWireValue1())
            self.update_column_heights(curr_column=self.N,
                                       curr_height_change=1)

        # Perform reduction until all columns have 2 or less bits in them
        while not all(height <= 2 for (height, *_) in self.columns):
            col = 0
            while col < len(self.columns):
                # If column has exactly 3 bits in height and all previous columns has maximum of 2 bits in height, combine them in a half adder
                if self.get_column_height(col) == 3 and all(
                        height <= 2
                        for (height, *_) in self.columns[0:col - 1]):
                    # Add half adder and also AND/NAND gates if neccesarry (via add_column_wire invocation) into list of circuit components
                    obj_adder = HalfAdder(
                        self.add_column_wire(column=col, bit=0),
                        self.add_column_wire(column=col, bit=1),
                        prefix=self.prefix + "_ha" +
                        str(self.get_instance_num(cls=HalfAdder)))
                    self.add_component(obj_adder)

                    # Update the number of current and next column wires
                    self.update_column_heights(curr_column=col,
                                               curr_height_change=-1,
                                               next_column=col + 1,
                                               next_height_change=1)

                    # Update current and next column wires arrangement
                    #   add ha's generated sum to the bottom of current column
                    #   add ha's generated cout to the top of next column
                    self.update_column_wires(
                        curr_column=col,
                        next_column=col + 1,
                        adder=self.get_previous_component(1))

                # If column has more than 3 bits in height, combine them in a full adder
                elif self.get_column_height(col) > 3:
                    # Add full adder and also AND/NAND gates if neccesarry (via add_column_wire invocation) into list of circuit components
                    obj_adder = FullAdder(
                        self.add_column_wire(column=col, bit=0),
                        self.add_column_wire(column=col, bit=1),
                        self.add_column_wire(column=col, bit=2),
                        prefix=self.prefix + "_fa" +
                        str(self.get_instance_num(cls=FullAdder)))
                    self.add_component(obj_adder)

                    # Update the number of current and next column wires
                    self.update_column_heights(curr_column=col,
                                               curr_height_change=-2,
                                               next_column=col + 1,
                                               next_height_change=1)

                    # Update current and next column wires arrangement
                    #   add fa's generated sum to the bottom of current column
                    #   add fa's generated cout to the top of next column
                    self.update_column_wires(
                        curr_column=col,
                        next_column=col + 1,
                        adder=self.get_previous_component(1))
                col += 1

        # Output generation
        # First output bit from single first pp AND gate
        self.out.connect(0, self.add_column_wire(column=0, bit=0))
        # Final addition of remaining bits
        # 1 bit multiplier case
        if self.N == 1:
            self.out.connect(1, ConstantWireValue0())
            return
        # 2 bit multiplier case
        elif self.N == 2:
            obj_ha = HalfAdder(self.add_column_wire(column=1, bit=0),
                               self.add_column_wire(column=1, bit=1),
                               prefix=self.prefix + "_ha" +
                               str(self.get_instance_num(cls=HalfAdder)))
            self.add_component(obj_ha)
            self.out.connect(1, obj_ha.get_sum_wire())

            obj_fa = FullAdder(self.get_previous_component().get_carry_wire(),
                               self.add_column_wire(column=2, bit=0),
                               self.add_column_wire(column=2, bit=1),
                               prefix=self.prefix + "_fa" +
                               str(self.get_instance_num(cls=FullAdder)))
            self.add_component(obj_fa)
            self.out.connect(2, obj_fa.get_sum_wire())
            self.out.connect(3, obj_fa.get_carry_wire())

        # Final addition of remaining bits using chosen unsigned multi bit adder
        else:
            # Obtain proper adder name with its bit width (columns bit pairs minus the first alone bit)
            adder_prefix = self.prefix + "_" + unsigned_adder_class_name(
                a=a, b=b).prefix + str(len(self.columns) - 1)

            adder_a = Bus(prefix=f"{adder_prefix}_a",
                          wires_list=[
                              self.add_column_wire(column=col, bit=0)
                              for col in range(1, len(self.columns))
                          ])
            adder_b = Bus(prefix=f"{adder_prefix}_b",
                          wires_list=[
                              self.add_column_wire(column=col, bit=1)
                              for col in range(1, len(self.columns))
                          ])
            final_adder = unsigned_adder_class_name(a=adder_a,
                                                    b=adder_b,
                                                    prefix=adder_prefix)
            self.add_component(final_adder)

            [
                self.out.connect(o,
                                 final_adder.out.get_wire(o - 1),
                                 inserted_wire_desired_index=o - 1)
                for o in range(1, len(self.out.bus))
            ]

        # 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)
Exemplo n.º 9
0
    def __init__(self,
                 a: Wire = Wire(name="a"),
                 b: Wire = Wire(name="b"),
                 c: Wire = Wire(name="bin"),
                 prefix: str = "fs"):
        super().__init__(a, b, c, prefix)
        # 2 wires for component's bus output (difference, bout)
        self.out = Bus(self.prefix + "_out", 2)

        # Difference
        xor_obj = XorGate(a=self.a,
                          b=self.b,
                          prefix=self.prefix + "_xor" +
                          str(self.get_instance_num(cls=XorGate)),
                          parent_component=self)
        self.add_component(xor_obj)

        not_obj = NotGate(a=self.a,
                          prefix=self.prefix + "_not" +
                          str(self.get_instance_num(cls=NotGate)),
                          parent_component=self)
        self.add_component(not_obj)

        and_obj = AndGate(a=not_obj.out,
                          b=self.b,
                          prefix=self.prefix + "_and" +
                          str(self.get_instance_num(cls=AndGate)),
                          parent_component=self)
        self.add_component(and_obj)

        difference_xor = XorGate(a=self.c,
                                 b=xor_obj.out,
                                 prefix=self.prefix + "_xor" +
                                 str(self.get_instance_num(cls=XorGate)),
                                 outid=0,
                                 parent_component=self)
        self.add_component(difference_xor)
        self.out.connect(0, difference_xor.out)

        # Borrow out
        not_obj = NotGate(a=xor_obj.out,
                          prefix=self.prefix + "_not" +
                          str(self.get_instance_num(cls=NotGate)),
                          parent_component=self)
        self.add_component(not_obj)

        and_obj = AndGate(a=not_obj.out,
                          b=self.c,
                          prefix=self.prefix + "_and" +
                          str(self.get_instance_num(cls=AndGate)),
                          parent_component=self)
        self.add_component(and_obj)

        borrow_out_or = OrGate(a=and_obj.out,
                               b=self.get_previous_component(4).out,
                               prefix=self.prefix + "_or" +
                               str(self.get_instance_num(cls=OrGate)),
                               outid=1,
                               parent_component=self)
        self.add_component(borrow_out_or)
        self.out.connect(1, borrow_out_or.out)
    def __init__(self, a: Bus, b: Bus, prefix: str = "s_arrmul"):
        super().__init__()
        self.c_data_type = "int64_t"
        self.N = max(a.N, b.N)
        self.prefix = prefix
        self.a = Bus(prefix=a.prefix, wires_list=a.bus)
        self.b = Bus(prefix=b.prefix, wires_list=b.bus)

        # 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)

        # Output wires for multiplication product
        self.out = Bus(self.prefix + "_out", self.N * 2)

        # Gradual generation of partial products
        for b_multiplier_index in range(self.N):
            for a_multiplicand_index in range(self.N):
                # AND and NAND gates generation for calculation of partial products and sign extension
                if (b_multiplier_index == self.N - 1
                        and a_multiplicand_index != self.N - 1) or (
                            b_multiplier_index != self.N - 1
                            and a_multiplicand_index == self.N - 1):
                    obj_nand = NandGate(self.a.get_wire(a_multiplicand_index),
                                        self.b.get_wire(b_multiplier_index),
                                        prefix=self.prefix + "_nand" +
                                        str(a_multiplicand_index) + "_" +
                                        str(b_multiplier_index),
                                        parent_component=self)
                    self.add_component(obj_nand)
                else:
                    obj_and = AndGate(self.a.get_wire(a_multiplicand_index),
                                      self.b.get_wire(b_multiplier_index),
                                      prefix=self.prefix + "_and" +
                                      str(a_multiplicand_index) + "_" +
                                      str(b_multiplier_index),
                                      parent_component=self)
                    self.add_component(obj_and)

                if b_multiplier_index != 0:
                    previous_product = self.components[
                        a_multiplicand_index +
                        b_multiplier_index].out if b_multiplier_index == 1 else self.get_previous_partial_product(
                            a_index=a_multiplicand_index,
                            b_index=b_multiplier_index)
                    # HA generation for first 1-bit adder in each row starting from the second one
                    if a_multiplicand_index == 0:
                        obj_adder = HalfAdder(
                            self.get_previous_component().out,
                            previous_product,
                            prefix=self.prefix + "_ha" +
                            str(a_multiplicand_index) + "_" +
                            str(b_multiplier_index))
                        self.add_component(obj_adder)
                        # Product generation
                        self.out.connect(b_multiplier_index,
                                         obj_adder.get_sum_wire())

                    # FA generation
                    else:
                        # Constant wire with value 1 used at the last FA in second row (as one of its inputs) for signed multiplication (based on Baugh Wooley algorithm)
                        if a_multiplicand_index == self.N - 1 and b_multiplier_index == 1:
                            previous_product = ConstantWireValue1()

                        obj_adder = FullAdder(
                            self.get_previous_component().out,
                            previous_product,
                            self.get_previous_component(
                                number=2).get_carry_wire(),
                            prefix=self.prefix + "_fa" +
                            str(a_multiplicand_index) + "_" +
                            str(b_multiplier_index))
                        self.add_component(obj_adder)

                # PRODUCT GENERATION
                if a_multiplicand_index == 0 and b_multiplier_index == 0:
                    self.out.connect(a_multiplicand_index, obj_and.out)

                    # 1 bit multiplier case
                    if a_multiplicand_index == self.N - 1:
                        obj_nor = NorGate(ConstantWireValue1(),
                                          self.get_previous_component().out,
                                          prefix=self.prefix +
                                          "_nor_zero_extend",
                                          parent_component=self)
                        self.add_component(obj_nor)

                        self.out.connect(a_multiplicand_index + 1, obj_nor.out)

                elif b_multiplier_index == self.N - 1:
                    self.out.connect(b_multiplier_index + a_multiplicand_index,
                                     obj_adder.get_sum_wire())

                    if a_multiplicand_index == self.N - 1:
                        obj_xor = XorGate(
                            self.get_previous_component().get_carry_wire(),
                            ConstantWireValue1(),
                            prefix=self.prefix + "_xor" +
                            str(a_multiplicand_index + 1) + "_" +
                            str(b_multiplier_index),
                            parent_component=self)
                        self.add_component(obj_xor)

                        self.out.connect(self.out.N - 1, obj_xor.out)