Example #1
0
def benchmark_data(benchmark_file_path, format='bench'):
    """
    Function to find benchmark data

    Arguments:
        benchmark_file_path {string} -- Path to the benchmark design file

    Keyword Arguments:
        format {string} -- The format of the design file (default: {'bench'})

    Raises:
        ValueError: If format is unknown

    Returns:
        metrics [dictionary] -- Returns the metrics of the benchmark (in,out,dff,and,or,etc.)
    """

    # Read design from file
    if format == 'bench':
        circuit = read.bench(benchmark_file_path)
    elif format == 'verilog':
        circuit = read.verilog(benchmark_file_path)
    elif format == 'vhdl' or format == 'vhd':
        circuit = read.vhdl(benchmark_file_path)
    elif format == 'smv':
        circuit = read.smv(benchmark_file_path)
    else:
        raise ValueError(f"Unknown format {format}")

    # Find and return metrics
    metrics = {}
    metrics['input'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'INPUT'
    ])
    metrics['output'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'OUTPUT'
    ])
    metrics['dff'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'DFF'
    ])
    metrics['mux'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'MUX'
    ])
    metrics['and'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'AND'
    ])
    metrics['nand'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'NAND'
    ])
    metrics['or'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'OR'
    ])
    metrics['nor'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'NOR'
    ])
    metrics['xor'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'XOR'
    ])
    metrics['xnor'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'XNOR'
    ])
    metrics['not'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'NOT'
    ])
    metrics['buff'] = len([
        signal for signal, sigattr in circuit.nodes.items()
        if sigattr['type'] == 'BUFF' or sigattr['type'] == 'BUF'
    ])
    return metrics
Example #2
0
def test_and_tree_bench():
    G_in = read.bench(os.path.join(netlists, 'and_tree.bench'))
    write.verilog(G_in, 'temp')
    G = read.verilog('temp.v')
    assert nx.is_isomorphic(G, G_in, lambda a, b: a['type'] == b['type'])
    os.remove('temp.v')
Example #3
0
def skew(netlist, skew_file_path):
    """
    Function to extract inputs, outputs, internal signals and gate types to find probability skew of all signals in the circuit.

    Arguments:
        netlist {networkx graph} : Graph generated by reading design using the read functions
        skew_file_path {string} : Path to file where the skew values are written. CSV file is preferable.

    Returns:
        None
    """

    # Function to calculate skew of output of a gate
    def calculate_gate_skew(gate_type, gate_input_skew_list):
        """
        Function that returns probability skew of a single gate output w.r.t skew of gate inputs based on the gate type.
        Supported gate types - AND, NAND, OR, NOR, NOT, BUFFER, DFF, 2-input XOR, 2-input XNOR, 2-input MUX
        :param gate_type: (string) highlights type of gate
        :param gate_input_skew_list: (list of numbers) probability skew values corresponding to each gate input
        :return: output_skew: (number) probability skew value of gate output
        """

        output_skew = 1
        gate_type = gate_type.strip()
        single_ip = True if len(gate_input_skew_list) < 2 else False
        if gate_type == "AND":
            if single_ip:
                return gate_input_skew_list[0]
            for signal_skew in gate_input_skew_list:
                output_skew *= (0.5 + signal_skew)
            output_skew -= 0.5
        elif gate_type == "NAND":
            if single_ip:
                return -1 * gate_input_skew_list[0]
            for signal_skew in gate_input_skew_list:
                output_skew *= (0.5 + signal_skew)
            output_skew = 0.5 - output_skew
        elif gate_type == "OR":
            if single_ip:
                return gate_input_skew_list[0]
            for signal_skew in gate_input_skew_list:
                output_skew *= (0.5 - signal_skew)
            output_skew = 0.5 - output_skew
        elif gate_type == "NOR":
            if single_ip:
                return -1 * gate_input_skew_list[0]
            for signal_skew in gate_input_skew_list:
                output_skew *= (0.5 - signal_skew)
            output_skew -= 0.5
        elif gate_type == "XOR":
            if single_ip:
                return -0.5
            output_skew = -2 * gate_input_skew_list[0] * gate_input_skew_list[1]
        elif gate_type == "XNOR":
            if single_ip:
                return 0.5
            output_skew = 2 * gate_input_skew_list[0] * gate_input_skew_list[1]
        elif gate_type == "MUX":
            if single_ip:
                return gate_input_skew_list[0]
            output_skew = (
                gate_input_skew_list[0] *
                (gate_input_skew_list[2] - gate_input_skew_list[1])) + (
                    (gate_input_skew_list[1] + gate_input_skew_list[2]) / 2)
        elif gate_type == "NOT":
            output_skew = -1 * gate_input_skew_list[0]
        elif gate_type == "BUFF" or gate_type == "BUF" or gate_type == "DFF" or gate_type == "OUTPUT":
            output_skew = gate_input_skew_list[0]
        else:
            print("Error : gate_type is " + gate_type)
            output_skew = 0
        return output_skew

    # Read netlist file if needed
    if isinstance(netlist, str):
        assert '.' in netlist, f"Design file extension must be provided"
        file_type = netlist[netlist.rfind('.') + 1:].strip()
        if file_type == 'bench':
            netlist = read.bench(netlist)
        elif file_type == 'verilog':
            netlist = read.verilog(netlist)
        else:
            raise ValueError(
                f"Skew calculation of design type {file_type} is not supported"
            )

    # Topologically sort the netlist
    try:
        sorted_nodes = list(nx.topological_sort(netlist))
    except (nx.NetworkXError, nx.NetworkXUnfeasible):
        raise ValueError("Netlist must be a DAG")

    # Find inputs, outputs and store file in list
    inputs = [
        signal for signal in netlist.nodes
        if netlist.nodes[signal]['type'] == 'INPUT'
    ]
    probability_skew = {}
    probability_skew = probability_skew.fromkeys(inputs, 0)

    # Find probability skew of all nodes in the netlist
    for signal in sorted_nodes:
        if signal in probability_skew:
            continue
        fanin = list(netlist.predecessors(signal))
        assert all(
            net in probability_skew for net in fanin
        ), f"Skew of some of the fan-in signals of {signal} has not been calculated"
        input_skew_list = [probability_skew[net] for net in fanin]
        probability_skew[signal] = calculate_gate_skew(
            netlist.nodes[signal]['type'], input_skew_list)
    assert all(signal in probability_skew for signal in sorted_nodes)

    # Write probability values to skew file
    if skew_file_path:
        with open(skew_file_path, 'w') as skew_file:
            skew_file.write("net,Prob0,Prob1\n")
            for signal in sorted(probability_skew.keys()):
                if netlist.nodes[signal]['type'] == 'OUTPUT':
                    continue
                p0, p1 = 0.5 - probability_skew[signal], probability_skew[
                    signal] + 0.5
                skew_file.write(signal + "," + str(p0) + "," + str(p1) + "\n")

    return probability_skew
Example #4
0
def test_buff_not_bench():
    G_bench = read.bench(os.path.join(netlists, 'buff_not.bench'))
    G_verilog = read.verilog(os.path.join(netlists, 'buff_not.v'))
    assert G_verilog.nodes['N3']['type'] == 'BUFF'
    assert G_verilog.nodes['N4']['type'] == 'NOT'
    assert nx.is_isomorphic(G_bench, G_verilog, lambda a, b: a['type'] == b['type'])
Example #5
0
def pointfunc(design_file_path, enc_type, key_size, h_value,
              encrypted_file_path):
    """Function to perform point-function based encryption.
    
    Arguments:
        design_file_path {string} -- Path to unencrypted verilog file
        enc_type {string} -- Encryption code (SR/NR/TR/FR)
        key_size {int} -- Key size
        h_value {int} -- Hamming distance for SFLL-HD (only applies when enc_type == FR)
        encrypted_file_path {string} -- Path to encrypted verilog file
    
    Raises:
        ValueError: If invalid encryption code is given
    
    Returns:
        bool -- True if encryption was successful; False if encryption was unsuccessful
    """

    if enc_type not in ['SR', 'NR', 'TR', 'FR']:
        raise ValueError(f"Invalid enctype {enc_type}")

    print(f'Encrypting circuit with {key_size} keys ', end='', flush=True)

    # Read original circuit
    OriginalCircuit = read.verilog(design_file_path)
    primary_outputs = {
        sigattr['name']
        for signal, sigattr in OriginalCircuit.nodes.items()
        if sigattr['type'] == 'OUTPUT'
    }
    original_primary_inputs = [
        sigattr['name'] for signal, sigattr in OriginalCircuit.nodes.items()
        if sigattr['type'] == 'INPUT'
    ]

    # Select output to lock
    sat_vul_key_size = 0
    sat_res_key_size = key_size
    fanin = []
    invalid_pi = set()
    primary_output = ''
    while True:
        valid_po = primary_outputs.difference(invalid_pi)
        if valid_po:
            primary_output = random.choice(list(valid_po))
        else:
            raise ValueError(
                f"No PO found with enough PIs in its ICOI for {key_size} keys")
        icoi = find.input_coi([primary_output],
                              OriginalCircuit)[primary_output]
        fanin = [
            signal for signal in icoi['PI']
            if not signal.startswith('keyinput')
        ]  # exclude key inputs from a previous round of logic locking
        if len(fanin) >= sat_res_key_size:
            break
        invalid_pi.add(primary_output)

    # Insert XOR gate(s) before the selected output
    po_replace = f'{primary_output}_in'
    OriginalCircuit.add_node(
        po_replace,
        name=po_replace,
        type=OriginalCircuit.nodes[primary_output]['type'])
    OriginalCircuit.add_edges_from(
        itertools.product(OriginalCircuit.predecessors(primary_output),
                          [po_replace]))
    OriginalCircuit.remove_edges_from(
        itertools.product(OriginalCircuit.predecessors(primary_output),
                          [primary_output]))
    OriginalCircuit.nodes[primary_output]['type'] = 'XOR'
    if enc_type in ['SR', 'NR']:
        # SATLock and AntiSAT use a single flip_signal
        flip_signal = 'flip_signal'
        top_wires = [flip_signal]
        OriginalCircuit.add_edge(po_replace, primary_output)
        OriginalCircuit.add_node(flip_signal, name=flip_signal, type='INPUT')
        OriginalCircuit.add_edge(flip_signal, primary_output)
    elif enc_type in ['TR', 'FR']:
        # TTLL and SFLL use two flip signals
        perturb_signal, restore_signal = 'perturb_signal', 'restore_signal'
        top_wires = [perturb_signal, restore_signal]
        po_perturb = f'{primary_output}_pert'
        OriginalCircuit.add_node(perturb_signal,
                                 name=perturb_signal,
                                 type='INPUT')
        OriginalCircuit.add_node(restore_signal,
                                 name=restore_signal,
                                 type='INPUT')
        OriginalCircuit.add_node(po_perturb, name=po_perturb, type='XOR')
        OriginalCircuit.add_edge(po_replace, po_perturb)
        OriginalCircuit.add_edge(perturb_signal, po_perturb)
        OriginalCircuit.add_edge(po_perturb, primary_output)
        OriginalCircuit.add_edge(restore_signal, primary_output)

    # Write circuit to temp file (converts the circuit into a module)
    temp_file_path = encrypted_file_path
    write.verilog(OriginalCircuit, temp_file_path)

    print_progress_step()

    # Select SAT resistant inputs
    if enc_type == 'NR':
        pi_count = int(sat_res_key_size /
                       2) + 1 if sat_res_key_size % 2 else int(
                           sat_res_key_size / 2)
    else:
        pi_count = sat_res_key_size
    sat_res_inputs = random.sample(fanin, pi_count)
    assert len(sat_res_inputs) == len(set(sat_res_inputs))

    # Generate random key value
    sat_res_key_value = format(random.getrandbits(sat_res_key_size),
                               f'0{sat_res_key_size}b')

    # Generate key inputs
    sat_res_key_inputs = [
        f'keyinput{keybit}'
        for keybit in range(sat_vul_key_size, sat_vul_key_size +
                            sat_res_key_size)
    ]

    # Construct verilog modules and instantiations
    verilog_modules = ''
    verilog_instantiations = ''

    # Build main module and instantiation
    main_module_name = Path(temp_file_path).stem
    with open(temp_file_path, 'r') as temp_file:
        main_module = temp_file.read()
    os.remove(temp_file_path)

    main_block_outputs = [
        sigattr['name'] for signal, sigattr in OriginalCircuit.nodes.items()
        if sigattr['type'] == 'OUTPUT'
    ]
    main_block_inputs = [
        sigattr['name'] for signal, sigattr in OriginalCircuit.nodes.items()
        if sigattr['type'] == 'INPUT'
    ]

    verilog_modules += main_module
    verilog_instantiations += instantiation_block(main_module_name, 'main',
                                                  main_block_inputs,
                                                  main_block_outputs)

    if enc_type in ['SR', 'NR']:
        # Build SatHard module and instantiation
        if enc_type == 'SR':
            flip_logic = f'\n  assign {flip_signal} = ( (keyinputs!=keyvalue) & (sat_res_inputs==keyinputs) ) ? \'b1 : \'b0;'
        elif enc_type == 'NR':
            flip_logic = (
                f'  wire g, g_bar;\n'
                f'  assign g = &(keyinputs[{pi_count-1}:0] ^ sat_res_inputs ^ keyvalue[{pi_count-1}:0]);\n'
                f'  assign g_bar = ~&(keyinputs[{sat_res_key_size-1}:{pi_count}] ^ sat_res_inputs ^ keyvalue[{sat_res_key_size-1}:{pi_count}]);\n'
                f'  assign {flip_signal} = g & g_bar;\n')
        sat_hard_block = (
            f'  //SatHard key={sat_res_key_value}\n'
            f'  wire [{pi_count-1}:0] sat_res_inputs;\n'
            f'  wire [{sat_res_key_size-1}:0] keyinputs, keyvalue;\n'
            f'  assign sat_res_inputs[{pi_count-1}:0] = {{{unroll(sat_res_inputs)}}};\n'
            f'  assign keyinputs[{sat_res_key_size-1}:0] = {{{unroll(sat_res_key_inputs)}}};\n'
            f'  assign keyvalue[{sat_res_key_size-1}:0] = {sat_res_key_size}\'b{sat_res_key_value};\n\n'
            f'{flip_logic}\n')
        sat_hard_block_inputs = sat_res_inputs + sat_res_key_inputs
        sat_hard_block_outputs = [flip_signal]

        verilog_modules += '\n' + module_block(
            'SatHard', sat_hard_block_inputs, sat_hard_block_outputs,
            sat_hard_block,
            '/*************** SatHard block ***************/\n')
        verilog_instantiations += instantiation_block('SatHard', 'flip',
                                                      sat_hard_block_inputs,
                                                      sat_hard_block_outputs)

    elif enc_type in ['TR', 'FR']:
        # Build Pertub and Restore modules and instantiations
        if enc_type == 'TR':
            perturb_logic = (
                f'  assign {perturb_signal} = (sat_res_inputs == keyvalue) ? \'b1 : \'b0;\n'
            )
            restore_logic = (
                f'  assign {restore_signal} = (sat_res_inputs == keyinputs) ? \'b1 : \'b0;\n'
            )
        elif enc_type == 'FR':
            assert 0 <= h_value <= key_size
            perturb_logic = (
                f"{ham_dist_block('sat_res_inputs', 'keyvalue', pi_count, 'ham_dist_peturb')}"
                f'  assign {perturb_signal} =  (ham_dist_peturb=={h_value}) ? \'b1 : \'b0;\n'
            )
            restore_logic = (
                f"{ham_dist_block('sat_res_inputs', 'keyinputs', pi_count, 'ham_dist_restore')}"
                f'  assign {restore_signal} = (ham_dist_restore=={h_value}) ? \'b1 : \'b0;\n'
            )

        perturb_block = (
            f'  //SatHard key={sat_res_key_value}\n'
            f'  wire [{pi_count-1}:0] sat_res_inputs;\n'
            f'  wire [{sat_res_key_size-1}:0] keyvalue;\n'
            f'  assign sat_res_inputs[{pi_count-1}:0] = {{{unroll(sat_res_inputs)}}};\n'
            f'  assign keyvalue[{sat_res_key_size-1}:0] = {sat_res_key_size}\'b{sat_res_key_value};\n\n'
            f'{perturb_logic}\n')

        restore_block = (
            f'  //SatHard key={sat_res_key_value}\n'
            f'  wire [{pi_count-1}:0] sat_res_inputs;\n'
            f'  wire [{sat_res_key_size-1}:0] keyinputs;\n'
            f'  assign sat_res_inputs[{pi_count-1}:0] = {{{unroll(sat_res_inputs)}}};\n'
            f'  assign keyinputs[{sat_res_key_size-1}:0] = {{{unroll(sat_res_key_inputs)}}};\n'
            f'{restore_logic}\n')

        perturb_block_inputs = sat_res_inputs
        perturb_block_outputs = [perturb_signal]

        verilog_modules += '\n' + module_block(
            'Perturb', perturb_block_inputs, perturb_block_outputs,
            perturb_block, '/*************** Perturb block ***************/\n')
        verilog_instantiations += instantiation_block('Perturb', 'perturb',
                                                      perturb_block_inputs,
                                                      perturb_block_outputs)

        restore_block_inputs = sat_res_inputs + sat_res_key_inputs
        restore_block_outputs = [restore_signal]

        verilog_modules += '\n' + module_block(
            'Restore', restore_block_inputs, restore_block_outputs,
            restore_block, '/*************** Restore block ***************/\n')
        verilog_instantiations += instantiation_block('Restore', 'restore',
                                                      restore_block_inputs,
                                                      restore_block_outputs)

    print_progress_step()

    # Build top level module
    top_block = (f'  wire {unroll(top_wires)};\n\n'
                 f'{verilog_instantiations}')
    top_inputs = original_primary_inputs + sat_res_key_inputs
    top_outputs = primary_outputs
    top_module = module_block(main_module_name + '_top', top_inputs,
                              top_outputs, top_block,
                              '/*************** Top Level ***************/\n')
    verilog_modules = top_module + '\n' + verilog_modules

    # Write modules to output file
    with open(encrypted_file_path, 'w') as enc_file:
        enc_file.writelines(verilog_modules)
    print(' Complete', flush=True)

    return True
Example #6
0
def cyclic(design_file_path, enc_type, key_size, encrypted_file_path):
    """Function to perform cyclic encryption.
    
    Arguments:
        design_file_path {string} -- Path to unencrypted verilog file
        enc_type {string} -- Encryption code (CR/RC)
        key_size {int} -- Key size
        encrypted_file_path {string} -- Path to encrypted verilog file
    
    Raises:
        ValueError: If invalid encryption code is given
        SystemExit: If unencrypted circuit is not a DAG (loop-free combinational design)
    
    Returns:
        bool -- True if encryption was successful; False if encryption was unsuccessful
    """
    def find_path(Circuit, primary_inputs, primary_outputs, source, length,
                  invalid_gates, Relaxed):
        """Function to find path of certain length from source node
        
        Arguments:
            Circuit {networkx.DiGraph} -- Circuit represented as a DAG
            primary_inputs {list(strings)} -- List of primary inputs of the design
            primary_outputs {list(strings)} -- List of primary outputs of the design
            source {string} -- Source node from which path needs to be found
            length {int} -- Length of path to be found
            invalid_gates {list(strings)} -- List of nets that should not be in the returned path
            Relaxed {bool} -- False -> path should not have invalid gates; True -> path can have a few invalid gates
        
        Returns:
            networkx.DiGraph -- Path starting at <source> node containing <length> nodes
        """
        path = nx.DiGraph()
        current_node = source
        path.add_node(source)
        invalid_nodes = {source: set()}
        primary_signals = primary_inputs.union(primary_outputs)
        # print(f'finding path for {source}', flush=True)
        while len(path.nodes()) < length:
            fan_out = set(Circuit.successors(current_node)).difference(
                invalid_nodes[current_node]).difference(primary_signals)
            if not Relaxed:
                fan_out = fan_out.difference(invalid_gates)
            if fan_out:
                added_node = random.choice(list(fan_out))
                path.add_node(added_node)
                path.add_edge(current_node, added_node)
                current_node = added_node
                invalid_nodes[added_node] = set()
            else:
                if current_node == source:
                    path.remove_node(source)
                    break
                removal_node = current_node
                current_node = list(path.predecessors(removal_node))[0]
                path.remove_node(removal_node)
                del invalid_nodes[removal_node]
                invalid_nodes[current_node].add(removal_node)
        # print(path.nodes())
        return path

    if enc_type not in ['CR', 'RC']:
        raise ValueError(f"Invalid enctype {enc_type}")

    print(f'Encrypting circuit with {key_size} keys ', end='', flush=True)

    # Global variables
    DefaultLoopLength = 8
    primary_inputs, primary_outputs = set(), set()
    Relaxed = False

    LoopLength = DefaultLoopLength
    # Find keys to be used for SRCLock
    if enc_type == 'RC':
        new_key_size = int(0.8 * key_size)
        RC_keys = key_size - new_key_size
        key_size = new_key_size

    # Read the circuit
    OriginalCircuit = read.verilog(design_file_path)
    primary_inputs = {
        sigattr['name']
        for signal, sigattr in OriginalCircuit.nodes.items()
        if sigattr['type'] == 'INPUT'
    }
    primary_outputs = {
        sigattr['name']
        for signal, sigattr in OriginalCircuit.nodes.items()
        if sigattr['type'] == 'OUTPUT'
    }

    # Topological sort gates in circuit
    try:
        sorted_gates = nx.topological_sort(OriginalCircuit)
    except (nx.NetworkXError, nx.NetworkXUnfeasible) as err:
        raise SystemExit(
            f"Circuit must be a DAG.\nNetworkx returned error {err}")
    sorted_gates = [
        gate for gate in sorted_gates if OriginalCircuit.nodes[gate]['name']
        not in primary_inputs.union(primary_outputs)
    ]
    print_progress_step()

    # Find suitable locations for gates
    gate_outputs = []
    correct_inputs, wrong_inputs = {}, {}
    sgr_gates, sgr_correct_outputs = [], {}
    special_gates = []
    multiple_tries = 0
    r_gates = []
    while len(gate_outputs) < key_size:

        # Find looplength
        if key_size - len(gate_outputs) < LoopLength:
            loop_length = key_size - len(gate_outputs)
        else:
            loop_length = LoopLength

        # Find paths
        tries = 0
        while True:
            u_gate = random.choice(sorted_gates)
            if u_gate not in gate_outputs:
                tries += 1
                path = find_path(OriginalCircuit, primary_inputs,
                                 primary_outputs, u_gate, loop_length,
                                 gate_outputs, Relaxed)
                if path.nodes():
                    break
                if tries == 100:
                    break

        # If bad locations have been found
        if tries == 100:
            if multiple_tries == 3:
                LoopLength = int(LoopLength / 2)
                print('|', end='', flush=True)
            elif multiple_tries == 7:
                Relaxed = True
                print('|', end='', flush=True)
            elif multiple_tries == 10:
                print(' Incomplete', flush=True)
                return False
            gate_outputs = []
            correct_inputs, wrong_inputs = {}, {}
            sgr_gates, sgr_correct_outputs = [], {}
            multiple_tries += 1
            continue

        # Get gates in path
        gates_in_path = list(nx.topological_sort(path))
        assert gates_in_path[0] == u_gate, f'Path does not begin with u'
        gate_outputs += gates_in_path

        # Get random gates as wrong inputs for MUXes
        r_gates += list(
            random.choices(
                [gate for gate in sorted_gates if gate not in gates_in_path],
                k=(loop_length - 1)))

    # pdb.set_trace()
    r_gate_itr = 0
    sgr_gate_count = 0
    itr = 0
    for gate_output in gate_outputs:

        # Set correct input and wrong inputs
        if itr % LoopLength == 0:
            correct_inputs[gate_output] = random.choice(
                list(OriginalCircuit.predecessors(gate_output)))
            if itr + LoopLength > key_size:
                last_gate = key_size - 1
            else:
                last_gate = itr + LoopLength - 1
            wrong_inputs[gate_output] = gate_outputs[last_gate]
        else:
            correct_inputs[gate_output] = gate_outputs[itr - 1]
            wrong_inputs[gate_output] = r_gates[r_gate_itr]
            r_gate_itr += 1
        itr += 1

        # For last gate in loop
        if itr % LoopLength == 0 or itr == key_size:
            continue

        # Get gates with only one fanout
        if len(list(OriginalCircuit.successors(gate_output))) == 1:
            r_gate = random.choice([
                gate for gate in sorted_gates
                if gate not in gate_outputs + sgr_gates
            ])
            special_gates.append(gate_output)
            sgr_gates.append(r_gate)
            sgr_correct_outputs[r_gate] = random.choice(
                list(OriginalCircuit.predecessors(r_gate)))
            sgr_gate_count += 1
    print_progress_step()

    # Generate mux logic
    keyvalue = format(random.getrandbits(key_size), f'0{key_size}b')
    keyinputs = [f'keyinput{itr}' for itr in range(key_size)]
    mux_outputs = [f'muxed_{itr}' for itr in range(key_size)]
    mux2_outputs = [f'muxed2_{itr}' for itr in range(sgr_gate_count)]
    # print(len(mux2_outputs))
    assign_text = []
    sg_index = 0
    edges_to_be_removed = set()
    mux_index = 0
    for gate_output in gate_outputs:

        # Remove original connection
        if not OriginalCircuit.has_edge(correct_inputs[gate_output],
                                        gate_output):
            fanin = OriginalCircuit.predecessors(gate_output)
            mux_ins = 0
            for sig in fanin:
                if OriginalCircuit.nodes[sig]['name'].startswith('muxed_'):
                    correct_inputs[gate_output] = sig
                    mux_ins += 1
            assert mux_ins == 1
        OriginalCircuit.remove_edge(correct_inputs[gate_output], gate_output)

        # Add new connection from/to mux output
        mux_output = mux_outputs[mux_index]
        OriginalCircuit.add_node(mux_output, name=mux_output, type='REMOVE')
        OriginalCircuit.add_edge(correct_inputs[gate_output], mux_output)
        OriginalCircuit.add_edge(mux_output, gate_output)

        # Add key gate to circuit
        keyinput = keyinputs[mux_index]
        OriginalCircuit.add_node(keyinput, name=keyinput, type='INPUT')
        OriginalCircuit.add_edge(keyinput, mux_output)

        # Add assign logic
        if keyvalue[mux_index] == '1':
            one_input, zero_input = correct_inputs[gate_output], wrong_inputs[
                gate_output]
        else:
            one_input, zero_input = wrong_inputs[gate_output], correct_inputs[
                gate_output]
        assign_text.append(
            f'assign {mux_output} = {keyinput} ? {one_input} : {zero_input};\n'
        )

        mux_index += 1
        # Logic for special gates
        if gate_output in special_gates:
            mux2_output = mux2_outputs[sg_index]
            r_gate = sgr_gates[sg_index]
            if not OriginalCircuit.has_edge(sgr_correct_outputs[r_gate],
                                            r_gate):
                fanin = OriginalCircuit.predecessors(r_gate)
                mux_ins = 0
                for sig in fanin:
                    if OriginalCircuit.nodes[sig]['name'].startswith('muxed_'):
                        sgr_correct_outputs[r_gate] = sig
                        mux_ins += 1
                assert mux_ins == 1
            OriginalCircuit.remove_edge(sgr_correct_outputs[r_gate], r_gate)

            # Add mux2 to circuit
            if mux_index >= len(keyinputs):
                print(gate_outputs)
                print(gate_output)
                print(special_gates)
            keyinput = keyinputs[mux_index]
            OriginalCircuit.add_node(mux2_output,
                                     name=mux2_output,
                                     type='REMOVE')
            OriginalCircuit.add_edge(mux2_output, r_gate)
            OriginalCircuit.add_edge(sgr_correct_outputs[r_gate], mux2_output)
            OriginalCircuit.add_edge(keyinput, mux2_output)

            # Put correct value in assign logic
            if keyvalue[mux_index] == '1':
                one_input, zero_input = sgr_correct_outputs[
                    r_gate], gate_output
            else:
                one_input, zero_input = gate_output, sgr_correct_outputs[
                    r_gate]
            assign_text.append(
                f'assign {mux2_output} = {keyinput} ? {one_input} : {zero_input};\n'
            )

            sg_index += 1
            special_gates.remove(gate_output)

    OriginalCircuit.remove_edges_from(edges_to_be_removed)
    assert sg_index == sgr_gate_count
    print_progress_step()

    if True:
        # Write circuit to enc file
        write.verilog(OriginalCircuit, encrypted_file_path)
        print('. Completed', flush=True)
        with open(encrypted_file_path, 'r') as enc_file:
            encrypted_netlist = enc_file.readlines()

        # Add assign text to design file
        encrypted_circuit = [f'//key={keyvalue}\n']
        for line in encrypted_netlist:
            if 'endmodule' in line:
                encrypted_circuit += assign_text + ['\n']
            elif line.startswith('  remove'):
                continue
            else:
                pass
            encrypted_circuit.append(line)

        with open(encrypted_file_path, 'w') as enc_file:
            enc_file.writelines(encrypted_circuit)

    if enc_type == 'RC':

        # SRCLock logic here:
        def generateSuperCycles(filename, max_keys_added=1000, maxIter=1000):
            """Function to connect SRCLock super cycles from an existing cyclic encrypted circuit
            
            Arguments:
                filename {string} -- Path to cyclic encrypted verilog file
            
            Keyword Arguments:
                max_keys_added {int} -- Number of keys used for super cycle creation (default: {1000})
                maxIter {int} -- Maximum number of iterations when finding cycles (default: {1000})
            
            Raises:
                Exception: If no cycles are found in the design
            """

            #First, edit verilog file so it can be read as a graph
            originalFile = open(filename, 'r')
            tmpFilename = filename[:filename.find(".")] + "_tmp.v"
            tmpFile = open(tmpFilename, 'w')

            def closeFile(fileToClose, outString):
                fileToClose.seek(0)
                fileToClose.write(outString)
                fileToClose.truncate()
                fileToClose.close()

            fileString = originalFile.read()
            newEdges = []
            currentPosition = 0
            keyCount = 0

            #Find an comment out mux lines and save them to be reconnected later
            gateLabelPos = fileString.find("(")
            gateLabel = fileString[gateLabelPos + 1:gateLabelPos + 2]
            currentPosition = fileString.find("assign", currentPosition)
            if fileString[currentPosition - 1] != '/':
                fileString = fileString.replace("assign", "//assign")
                currentPosition = fileString.find("assign", currentPosition)
            existingMux = fileString[currentPosition:]
            while (currentPosition > -1):
                nodeNames = []
                muxedStringLoc = fileString.find("muxed", currentPosition)
                muxedString = fileString[muxedStringLoc:fileString.
                                         find(" ", muxedStringLoc)]
                nodeNames.append(muxedString)
                nodeNameLoc = fileString.find("keyinput", muxedStringLoc)
                nodeNames.append(
                    fileString[nodeNameLoc:fileString.find(" ", nodeNameLoc)])
                keyCountStr = nodeNames[1][8:]
                keyCount = int(keyCountStr)
                nodeNameLoc = fileString.find(" ", nodeNameLoc) + 1
                nodeNameLoc = fileString.find(" ", nodeNameLoc + 1) + 1
                nodeNames.append(
                    fileString[nodeNameLoc:fileString.find(" ", nodeNameLoc)])
                nodeNameLoc = fileString.find(" ", nodeNameLoc + 1) + 1
                nodeNameLoc = fileString.find(" ", nodeNameLoc + 1) + 1
                nodeNames.append(
                    fileString[nodeNameLoc:fileString.find(";", nodeNameLoc)])
                #print(nodeNames)
                newEdges.append(nodeNames)
                currentPosition = fileString.find("assign", nodeNameLoc)
            #print(keyCount)
            currentPosition = fileString.find("xor")

            #Find and remove 3+ input XOR gates that can't be read, to be reconnected later
            while (currentPosition > -1):
                nodeNames = []
                outLoc = fileString.find("(", currentPosition)
                gateString = fileString[outLoc:fileString.find(")", outLoc) +
                                        1]
                if (gateString.count(",") < 3):
                    currentPosition = fileString.find("xor", outLoc)
                    continue
                outNode = gateString[1:gateString.find(",")]
                nodeNames.append(outNode)
                commaPos = 0
                for i in range(0, 3):
                    commaPos = gateString.find(",", commaPos + 1)
                while (commaPos > -1):
                    gateToRemove = gateString[commaPos + 2:gateString.
                                              find(",", commaPos + 1)]
                    nodeNames.append(gateToRemove)
                    commaPos = gateString.find(",", commaPos + 1)
                newGateString = gateString
                for nodeToRemove in nodeNames[1:]:
                    newGateString = newGateString.replace(
                        ", " + nodeToRemove, "")
                fileString = fileString.replace(gateString, newGateString)
                newEdges.append(nodeNames)
                currentPosition = fileString.find("xor", outLoc)
            tmpFile.write(fileString)
            tmpFile.close()
            originalFile.close()

            #Read in verilog file as a directed graph
            circuit_graph = read.verilog(tmpFilename)

            #Reconnect edges removed in previous step
            for newEdge in newEdges:
                for i in range(1, len(newEdge)):
                    circuit_graph.add_edge(newEdge[i], newEdge[0])
            for node in circuit_graph.nodes:
                if not 'type' in circuit_graph.nodes[node].keys():
                    circuit_graph.nodes[node].update({
                        'type': 'NONE',
                        'name': node
                    })
            muxes = []
            randConeSignals = []
            cycleNum = 0

            #Find cycles and connect them into supercycles
            removedEdges = []
            iters = 0
            while len(muxes) < max_keys_added and iters < maxIter:
                for i in range(0, maxIter):
                    try:
                        cycle = nx.find_cycle(circuit_graph)
                    except:
                        #print("No more cycles", '\n')
                        break
                    else:
                        #print("Cycle Found: ", cycle, '\n')
                        #Find places to place MUX gates in the cycle
                        SCresult = findSCConnections(cycle, circuit_graph)
                        newMuxSignals = SCresult[0]
                        newRandSignals = SCresult[1]
                        repeatSignals = []

                        #If edge is already connected to another MUX, find a new connection
                        for muxSignal in newMuxSignals:
                            if (any(muxSignal in mux for mux in muxes)
                                    or muxSignal[0] in existingMux
                                    or muxSignal[1] in existingMux):
                                repeatSignals.append(muxSignal)
                                SCresult = findSCConnections(
                                    cycle, circuit_graph, repeatSignals)
                                newMuxSignals = SCresult[0]
                                newRandSignals = SCresult[1]
                        randrepeatSignals = []

                        #If adding randomly connected signals (probably not), repeat above for those
                        for randSignal in newRandSignals:
                            if randSignal in randConeSignals:
                                repeatSignals.append(randSignal)
                                SCresult = findSCConnections(
                                    cycle, circuit_graph, repeatSignals)
                                newMuxSignals = SCresult[0]
                                newRandSignals = SCresult[1]
                                SCresult = findSCConnections(
                                    cycle, circuit_graph)
                                newMuxSignals = SCresult[0]
                                newRandSignals = SCresult[1]
                        if len(newMuxSignals) == 2:
                            muxes.append(newMuxSignals)
                        randConeSignals.extend(newRandSignals)
                        #print("Current Mux Connections: ", muxes, '\n')
                        removedEdges.append([cycle[0][0], cycle[0][1]])
                        circuit_graph.remove_edge(cycle[0][0], cycle[0][1])
                        if len(muxes) > max_keys_added:
                            break
                if len(muxes) < max_keys_added:
                    for edge in removedEdges:
                        circuit_graph.add_edge(edge[0], edge[1])
                iters += 1
            #print(randConeSignals)
            muxes = muxes[:max_keys_added]
            print(muxes)
            if len(muxes) == 0:
                raise Exception('No cycles found')
                return
            muxLines = []
            signalsToReplace = []
            key = ""

            #print("Mux Lines:", '\n')

            #Create new MUX logic for the new verilog file
            def addMuxLines(firstIndex, secondIndex, keyBit):
                firstCorrect = muxes[secondIndex - keyBit][0 + keyBit][0]
                secondCorrect = muxes[firstIndex + keyBit][1 - keyBit][0]
                gateNo = i + 2 + keyCount
                muxLine1 = "assign muxed_" + str(gateNo) + " = keyinput" + str(
                    gateNo
                ) + " ? " + firstCorrect + " : " + secondCorrect + ";"
                muxLine2 = "assign muxed2_" + str(
                    gateNo) + " = keyinput" + str(
                        gateNo
                    ) + " ? " + secondCorrect + " : " + firstCorrect + ";"
                muxLines.append(muxLine1)
                muxLines.append(muxLine2)
                #print(muxLine1)
                #print(muxLine2)

            for i in range(-1, len(muxes) - 1):
                signalsToReplace.append(muxes[i][1])
                signalsToReplace.append(muxes[i + 1][0])
                keyBit = random.randint(0, 1)
                key = key + str(keyBit)
                addMuxLines(i, i + 1, keyBit)
            for i in range(0, len(randConeSignals), 2):
                signalsToReplace.append(randConeSignals[i])
                signalsToReplace.append(randConeSignals[i + 1])
                keyBit = random.randint(0, 1)
                key = key + str(keyBit)
                firstCorrect = randConeSignals[i + 1 - keyBit][0]
                secondCorrect = randConeSignals[i + keyBit][0]
                randGateNo = keyCount + len(muxes) + 1 + int(round(i / 2))
                muxLine1 = "assign muxed_" + str(
                    randGateNo) + " = keyinput" + str(
                        randGateNo
                    ) + " ? " + firstCorrect + " : " + secondCorrect + ";"
                muxLine2 = "assign muxed2_" + str(
                    randGateNo) + " = keyinput" + str(
                        randGateNo
                    ) + " ? " + secondCorrect + " : " + firstCorrect + ";"
                muxLines.append(muxLine1)
                muxLines.append(muxLine2)
            #print('\n', "Signals To Replace: ", signalsToReplace, '\n')
            print("RCkey=", key)

            #Write the new MUX logic to the verilog file
            originalFile = open(filename, 'r+')
            fileString = originalFile.read()
            fileString = fileString.replace("//assign", "assign")
            lastKeyinput = "keyinput" + str(keyCount) + ", "
            newKeyinputs = lastKeyinput
            lastMuxWire = "muxed_" + str(keyCount) + ";"
            newMuxWires = lastMuxWire[:-1] + ", "
            for i in range(0, int(len(muxLines) / 2)):
                index = i + keyCount + 1
                newKeyinputs = newKeyinputs + "keyinput" + str(i + keyCount +
                                                               1) + ", "
                newMuxWires = newMuxWires + "muxed_" + str(i + keyCount +
                                                           1) + ", "
                newMuxWires = newMuxWires + "muxed2_" + str(i + keyCount +
                                                            1) + ", "
            fileString = fileString.replace(lastKeyinput, newKeyinputs)
            fileString = fileString.replace(lastKeyinput[:-2] + ";",
                                            newKeyinputs[:-2] + ";")
            fileString = fileString.replace(lastMuxWire,
                                            newMuxWires[:-2] + ";")
            i = 0
            for signal in signalsToReplace:
                gateLoc = fileString.find("(" + signal[1])
                gateString = fileString[gateLoc:fileString.find(")", gateLoc)]
                muxLocation = muxLines[i].find("muxed")
                newSignal = muxLines[i][muxLocation:muxLines[i].
                                        find(" ", muxLocation)]
                newgateString = gateString.replace(signal[0], newSignal)
                fileString = fileString.replace(gateString, newgateString)
                i = i + 1
            fileString = fileString.replace("endmodule", "")
            for muxLine in muxLines:
                fileString = fileString + muxLine + '\n'
            fileString = fileString + '\n' + "endmodule" + '\n'
            keyLoc = fileString.find("key=")
            keyString = fileString[keyLoc:fileString.find("\n", keyLoc)]
            fileString = fileString.replace(keyString, keyString + key)
            closeFile(originalFile, fileString)
            os.remove(tmpFilename)

        def findSCConnections(cycle,
                              circuit_graph,
                              muxsignalsToIgnore=[],
                              randsignalsToIgnore=[],
                              doRandomSignals=False):
            """Function that iterates through a cycle to find suitable places to connect to other cycles
            
            Arguments:
                cycle {list} -- Cycle  (list of Networkx edges) to be connected to a supercycle
                circuit_graph {Networkx DiGraph} -- Networkx graph of the main circuit for reference
            
            Keyword Arguments:
                muxsignalsToIgnore {list} -- List of edges to be passed over while searching for connection points with other cycles (default: {[]})
                randsignalsToIgnore {list} -- List of edges to be passed over while searching for connection points with noncycle nets (if doRandomSignals is True) (default: {[]})
                doRandomSignals {bool} -- Decides whether or not to connect cycles to noncycle nets in the circuit (increases computation time) (default: {False})
            
            Returns:
                list -- Index 0: List of edges to turn into connection points to other cycles. Index 1: List of edges to connect to the current cycle.
            """

            #Iterates through a cycle to find suitable places to connect to other cycles for supercycles
            signalsToMux = []
            randomConeSignals = []
            for edge in cycle:
                randomIter = 0
                if (edge in signalsToMux) or (edge
                                              in muxsignalsToIgnore) or (any(
                                                  'muxed' in node
                                                  for node in edge)):
                    continue
                elif len(signalsToMux) < 2:
                    signalsToMux.append(edge)
                elif doRandomSignals and randomIter < 1:
                    randomIter = randomIter + 1
                    randomConeSignals.append(edge)
                    #print("Finding coi")
                    inputCoi = find.input_coi([edge[0]], circuit_graph)
                    #print("Found coi")
                    randNum = random.randrange(0, len(inputCoi[edge[0]]['IS']))
                    randomInternalSignal = inputCoi[edge[0]]['IS'][randNum]
                    randomInEdge = circuit_graph.in_edges(
                        randomInternalSignal)[0]
                    i = 0
                    maxIter = 1000
                    while (('keyinput' in randomInEdge[0]) or
                           ('muxed' in randomInEdge[0]) or
                           (randomInEdge
                            in randsignalsToIgnore)) and (i < maxIter):
                        randNum = random.randrange(
                            0, len(inputCoi[edge[0]]['IS']))
                        randomInternalSignal = inputCoi[edge[0]]['IS'][randNum]
                        randomInEdge = circuit_graph.in_edges(
                            randomInternalSignal)[0]
                        i = i + 1
                    randomConeSignals.append(randomInEdge)
            return [signalsToMux, randomConeSignals]

        generateSuperCycles(encrypted_file_path, max_keys_added=RC_keys)

    return True
Example #7
0
        def generateSuperCycles(filename, max_keys_added=1000, maxIter=1000):
            """Function to connect SRCLock super cycles from an existing cyclic encrypted circuit
            
            Arguments:
                filename {string} -- Path to cyclic encrypted verilog file
            
            Keyword Arguments:
                max_keys_added {int} -- Number of keys used for super cycle creation (default: {1000})
                maxIter {int} -- Maximum number of iterations when finding cycles (default: {1000})
            
            Raises:
                Exception: If no cycles are found in the design
            """

            #First, edit verilog file so it can be read as a graph
            originalFile = open(filename, 'r')
            tmpFilename = filename[:filename.find(".")] + "_tmp.v"
            tmpFile = open(tmpFilename, 'w')

            def closeFile(fileToClose, outString):
                fileToClose.seek(0)
                fileToClose.write(outString)
                fileToClose.truncate()
                fileToClose.close()

            fileString = originalFile.read()
            newEdges = []
            currentPosition = 0
            keyCount = 0

            #Find an comment out mux lines and save them to be reconnected later
            gateLabelPos = fileString.find("(")
            gateLabel = fileString[gateLabelPos + 1:gateLabelPos + 2]
            currentPosition = fileString.find("assign", currentPosition)
            if fileString[currentPosition - 1] != '/':
                fileString = fileString.replace("assign", "//assign")
                currentPosition = fileString.find("assign", currentPosition)
            existingMux = fileString[currentPosition:]
            while (currentPosition > -1):
                nodeNames = []
                muxedStringLoc = fileString.find("muxed", currentPosition)
                muxedString = fileString[muxedStringLoc:fileString.
                                         find(" ", muxedStringLoc)]
                nodeNames.append(muxedString)
                nodeNameLoc = fileString.find("keyinput", muxedStringLoc)
                nodeNames.append(
                    fileString[nodeNameLoc:fileString.find(" ", nodeNameLoc)])
                keyCountStr = nodeNames[1][8:]
                keyCount = int(keyCountStr)
                nodeNameLoc = fileString.find(" ", nodeNameLoc) + 1
                nodeNameLoc = fileString.find(" ", nodeNameLoc + 1) + 1
                nodeNames.append(
                    fileString[nodeNameLoc:fileString.find(" ", nodeNameLoc)])
                nodeNameLoc = fileString.find(" ", nodeNameLoc + 1) + 1
                nodeNameLoc = fileString.find(" ", nodeNameLoc + 1) + 1
                nodeNames.append(
                    fileString[nodeNameLoc:fileString.find(";", nodeNameLoc)])
                #print(nodeNames)
                newEdges.append(nodeNames)
                currentPosition = fileString.find("assign", nodeNameLoc)
            #print(keyCount)
            currentPosition = fileString.find("xor")

            #Find and remove 3+ input XOR gates that can't be read, to be reconnected later
            while (currentPosition > -1):
                nodeNames = []
                outLoc = fileString.find("(", currentPosition)
                gateString = fileString[outLoc:fileString.find(")", outLoc) +
                                        1]
                if (gateString.count(",") < 3):
                    currentPosition = fileString.find("xor", outLoc)
                    continue
                outNode = gateString[1:gateString.find(",")]
                nodeNames.append(outNode)
                commaPos = 0
                for i in range(0, 3):
                    commaPos = gateString.find(",", commaPos + 1)
                while (commaPos > -1):
                    gateToRemove = gateString[commaPos + 2:gateString.
                                              find(",", commaPos + 1)]
                    nodeNames.append(gateToRemove)
                    commaPos = gateString.find(",", commaPos + 1)
                newGateString = gateString
                for nodeToRemove in nodeNames[1:]:
                    newGateString = newGateString.replace(
                        ", " + nodeToRemove, "")
                fileString = fileString.replace(gateString, newGateString)
                newEdges.append(nodeNames)
                currentPosition = fileString.find("xor", outLoc)
            tmpFile.write(fileString)
            tmpFile.close()
            originalFile.close()

            #Read in verilog file as a directed graph
            circuit_graph = read.verilog(tmpFilename)

            #Reconnect edges removed in previous step
            for newEdge in newEdges:
                for i in range(1, len(newEdge)):
                    circuit_graph.add_edge(newEdge[i], newEdge[0])
            for node in circuit_graph.nodes:
                if not 'type' in circuit_graph.nodes[node].keys():
                    circuit_graph.nodes[node].update({
                        'type': 'NONE',
                        'name': node
                    })
            muxes = []
            randConeSignals = []
            cycleNum = 0

            #Find cycles and connect them into supercycles
            removedEdges = []
            iters = 0
            while len(muxes) < max_keys_added and iters < maxIter:
                for i in range(0, maxIter):
                    try:
                        cycle = nx.find_cycle(circuit_graph)
                    except:
                        #print("No more cycles", '\n')
                        break
                    else:
                        #print("Cycle Found: ", cycle, '\n')
                        #Find places to place MUX gates in the cycle
                        SCresult = findSCConnections(cycle, circuit_graph)
                        newMuxSignals = SCresult[0]
                        newRandSignals = SCresult[1]
                        repeatSignals = []

                        #If edge is already connected to another MUX, find a new connection
                        for muxSignal in newMuxSignals:
                            if (any(muxSignal in mux for mux in muxes)
                                    or muxSignal[0] in existingMux
                                    or muxSignal[1] in existingMux):
                                repeatSignals.append(muxSignal)
                                SCresult = findSCConnections(
                                    cycle, circuit_graph, repeatSignals)
                                newMuxSignals = SCresult[0]
                                newRandSignals = SCresult[1]
                        randrepeatSignals = []

                        #If adding randomly connected signals (probably not), repeat above for those
                        for randSignal in newRandSignals:
                            if randSignal in randConeSignals:
                                repeatSignals.append(randSignal)
                                SCresult = findSCConnections(
                                    cycle, circuit_graph, repeatSignals)
                                newMuxSignals = SCresult[0]
                                newRandSignals = SCresult[1]
                                SCresult = findSCConnections(
                                    cycle, circuit_graph)
                                newMuxSignals = SCresult[0]
                                newRandSignals = SCresult[1]
                        if len(newMuxSignals) == 2:
                            muxes.append(newMuxSignals)
                        randConeSignals.extend(newRandSignals)
                        #print("Current Mux Connections: ", muxes, '\n')
                        removedEdges.append([cycle[0][0], cycle[0][1]])
                        circuit_graph.remove_edge(cycle[0][0], cycle[0][1])
                        if len(muxes) > max_keys_added:
                            break
                if len(muxes) < max_keys_added:
                    for edge in removedEdges:
                        circuit_graph.add_edge(edge[0], edge[1])
                iters += 1
            #print(randConeSignals)
            muxes = muxes[:max_keys_added]
            print(muxes)
            if len(muxes) == 0:
                raise Exception('No cycles found')
                return
            muxLines = []
            signalsToReplace = []
            key = ""

            #print("Mux Lines:", '\n')

            #Create new MUX logic for the new verilog file
            def addMuxLines(firstIndex, secondIndex, keyBit):
                firstCorrect = muxes[secondIndex - keyBit][0 + keyBit][0]
                secondCorrect = muxes[firstIndex + keyBit][1 - keyBit][0]
                gateNo = i + 2 + keyCount
                muxLine1 = "assign muxed_" + str(gateNo) + " = keyinput" + str(
                    gateNo
                ) + " ? " + firstCorrect + " : " + secondCorrect + ";"
                muxLine2 = "assign muxed2_" + str(
                    gateNo) + " = keyinput" + str(
                        gateNo
                    ) + " ? " + secondCorrect + " : " + firstCorrect + ";"
                muxLines.append(muxLine1)
                muxLines.append(muxLine2)
                #print(muxLine1)
                #print(muxLine2)

            for i in range(-1, len(muxes) - 1):
                signalsToReplace.append(muxes[i][1])
                signalsToReplace.append(muxes[i + 1][0])
                keyBit = random.randint(0, 1)
                key = key + str(keyBit)
                addMuxLines(i, i + 1, keyBit)
            for i in range(0, len(randConeSignals), 2):
                signalsToReplace.append(randConeSignals[i])
                signalsToReplace.append(randConeSignals[i + 1])
                keyBit = random.randint(0, 1)
                key = key + str(keyBit)
                firstCorrect = randConeSignals[i + 1 - keyBit][0]
                secondCorrect = randConeSignals[i + keyBit][0]
                randGateNo = keyCount + len(muxes) + 1 + int(round(i / 2))
                muxLine1 = "assign muxed_" + str(
                    randGateNo) + " = keyinput" + str(
                        randGateNo
                    ) + " ? " + firstCorrect + " : " + secondCorrect + ";"
                muxLine2 = "assign muxed2_" + str(
                    randGateNo) + " = keyinput" + str(
                        randGateNo
                    ) + " ? " + secondCorrect + " : " + firstCorrect + ";"
                muxLines.append(muxLine1)
                muxLines.append(muxLine2)
            #print('\n', "Signals To Replace: ", signalsToReplace, '\n')
            print("RCkey=", key)

            #Write the new MUX logic to the verilog file
            originalFile = open(filename, 'r+')
            fileString = originalFile.read()
            fileString = fileString.replace("//assign", "assign")
            lastKeyinput = "keyinput" + str(keyCount) + ", "
            newKeyinputs = lastKeyinput
            lastMuxWire = "muxed_" + str(keyCount) + ";"
            newMuxWires = lastMuxWire[:-1] + ", "
            for i in range(0, int(len(muxLines) / 2)):
                index = i + keyCount + 1
                newKeyinputs = newKeyinputs + "keyinput" + str(i + keyCount +
                                                               1) + ", "
                newMuxWires = newMuxWires + "muxed_" + str(i + keyCount +
                                                           1) + ", "
                newMuxWires = newMuxWires + "muxed2_" + str(i + keyCount +
                                                            1) + ", "
            fileString = fileString.replace(lastKeyinput, newKeyinputs)
            fileString = fileString.replace(lastKeyinput[:-2] + ";",
                                            newKeyinputs[:-2] + ";")
            fileString = fileString.replace(lastMuxWire,
                                            newMuxWires[:-2] + ";")
            i = 0
            for signal in signalsToReplace:
                gateLoc = fileString.find("(" + signal[1])
                gateString = fileString[gateLoc:fileString.find(")", gateLoc)]
                muxLocation = muxLines[i].find("muxed")
                newSignal = muxLines[i][muxLocation:muxLines[i].
                                        find(" ", muxLocation)]
                newgateString = gateString.replace(signal[0], newSignal)
                fileString = fileString.replace(gateString, newgateString)
                i = i + 1
            fileString = fileString.replace("endmodule", "")
            for muxLine in muxLines:
                fileString = fileString + muxLine + '\n'
            fileString = fileString + '\n' + "endmodule" + '\n'
            keyLoc = fileString.find("key=")
            keyString = fileString[keyLoc:fileString.find("\n", keyLoc)]
            fileString = fileString.replace(keyString, keyString + key)
            closeFile(originalFile, fileString)
            os.remove(tmpFilename)