├── README.md └── atpg_combinational_5valued.py /README.md: -------------------------------------------------------------------------------- 1 | atpg-PODEM 2 | ========== 3 | 4 | This Automatic Test Pattern Generation (ATPG) program takes in a combinational circuit defined as a graph and a single stuck-at-fault expressed as either D or D(bar), and runs the 5-valued PODEM algorithm to determine whether the error can be propagated to the output for an input test vector, and also displays the state of the graph (including input test vector) for error propagation. 5 | 6 | The inputs are entred by hand within program itself, to run sample example, do ./atpg_combinational_5valued.py 7 | -------------------------------------------------------------------------------- /atpg_combinational_5valued.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import matplotlib.pyplot as plt 4 | import networkx as nx 5 | from collections import OrderedDict 6 | 7 | class MyOrderedDict(OrderedDict): 8 | def last(self): 9 | return list(self.items())[-1] 10 | def lastbutone(self): 11 | return list(self.items())[-2] 12 | 13 | def output_val(val_non_faulty, val_faulty): 14 | return{ 15 | '00':'0', 16 | '01':'D', 17 | '0x':'x', 18 | '10':'D_bar', 19 | '11':'1', 20 | '1x':'x', 21 | 'x0':'x', 22 | 'x1':'x', 23 | 'xx':'x', 24 | 25 | }[val_non_faulty + val_faulty] 26 | 27 | 28 | "This funtion is to calculate outputs of a gate out_node based on current values of its input. Returns output value" 29 | def calc_output(G, out_node): 30 | def or_three_valued(a, b): 31 | return{ 32 | '00': '0', 33 | '01': '1', 34 | '0x': 'x', 35 | '10': '1', 36 | '11': '1', 37 | '1x': '1', 38 | 'x0': 'x', 39 | 'x1': '1', 40 | 'xx': 'x', 41 | }[a + b] 42 | 43 | def and_three_valued(a, b): 44 | return{ 45 | '00': '0', 46 | '01': '0', 47 | '0x': '0', 48 | '10': '0', 49 | '11': '1', 50 | '1x': 'x', 51 | 'x0': '0', 52 | 'x1': 'x', 53 | 'xx': 'x', 54 | }[a + b] 55 | 56 | def not_three_valued(a): 57 | return{ 58 | '0': '1', 59 | '1': '0', 60 | 'x': 'x', 61 | }[a] 62 | 63 | def nand_three_valued(a, b): 64 | return not_three_valued(and_three_valued(a, b)) 65 | 66 | def nor_three_valued(a, b): 67 | return not_three_valued(or_three_valued(a, b)) 68 | 69 | def xor_three_valued(a, b): 70 | return or_three_valued(and_three_valued(a, not_three_valued(b)), and_three_valued(b, not_three_valued(a))) 71 | 72 | def xnor_three_valued(a, b): 73 | return not_three_valued(xor_three_valued(a, b)) 74 | 75 | def non_faulty(a): 76 | return{ 77 | '0':'0', 78 | '1':'1', 79 | 'x':'x', 80 | 'D':'0', 81 | 'D_bar':'1' 82 | }[a] 83 | 84 | def faulty(a): 85 | return{ 86 | '0':'0', 87 | '1':'1', 88 | 'x':'x', 89 | 'D':'1', 90 | 'D_bar':'0' 91 | }[a] 92 | def and_gate(a, b): 93 | a_non_faulty = non_faulty(a) 94 | b_non_faulty = non_faulty(a) 95 | val_non_faulty = and_three_valued(a_non_faulty, b_non_faulty) 96 | a_faulty = faulty(a) 97 | b_faulty = faulty(b) 98 | val_faulty = and_three_valued(a_faulty, b_faulty) 99 | return output_val(val_non_faulty, val_faulty) 100 | 101 | def or_gate(a, b): 102 | a_non_faulty = non_faulty(a) 103 | b_non_faulty = non_faulty(a) 104 | val_non_faulty = or_three_valued(a_non_faulty, b_non_faulty) 105 | a_faulty = faulty(a) 106 | b_faulty = faulty(b) 107 | val_faulty = or_three_valued(a_faulty, b_faulty) 108 | return output_val(val_non_faulty, val_faulty) 109 | 110 | def nand_gate(a, b): 111 | a_non_faulty = non_faulty(a) 112 | b_non_faulty = non_faulty(a) 113 | val_non_faulty = nand_three_valued(a_non_faulty, b_non_faulty) 114 | a_faulty = faulty(a) 115 | b_faulty = faulty(b) 116 | val_faulty = nand_three_valued(a_faulty, b_faulty) 117 | return output_val(val_non_faulty, val_faulty) 118 | 119 | def nor_gate(a, b): 120 | a_non_faulty = non_faulty(a) 121 | b_non_faulty = non_faulty(a) 122 | val_non_faulty = nor_three_valued(a_non_faulty, b_non_faulty) 123 | a_faulty = faulty(a) 124 | b_faulty = faulty(b) 125 | val_faulty = nor_three_valued(a_faulty, b_faulty) 126 | return output_val(val_non_faulty, val_faulty) 127 | 128 | def xor_gate(a, b): 129 | a_non_faulty = non_faulty(a) 130 | b_non_faulty = non_faulty(a) 131 | val_non_faulty = xor_three_valued(a_non_faulty, b_non_faulty) 132 | a_faulty = faulty(a) 133 | b_faulty = faulty(b) 134 | val_faulty = xor_three_valued(a_faulty, b_faulty) 135 | return output_val(val_non_faulty, val_faulty) 136 | 137 | def xnor_gate(a, b): 138 | a_non_faulty = non_faulty(a) 139 | b_non_faulty = non_faulty(a) 140 | val_non_faulty = xnor_three_valued(a_non_faulty, b_non_faulty) 141 | a_faulty = faulty(a) 142 | b_faulty = faulty(b) 143 | val_faulty = xnor_three_valued(a_faulty, b_faulty) 144 | return output_val(val_non_faulty, val_faulty) 145 | 146 | def not_gate(a): 147 | a_non_faulty = non_faulty(a) 148 | val_non_faulty = not_three_valued(a_non_faulty) 149 | a_faulty = faulty(a) 150 | val_faulty = not_three_valued(a_faulty) 151 | return output_val(val_non_faulty, val_faulty) 152 | 153 | gate_type = out_node[1]['gatetype'] 154 | in_edge_list = G.in_edges(out_node[0], data=True) 155 | first_edge = in_edge_list[0] 156 | val = first_edge[2]['value'] 157 | if(gate_type == 'not'): 158 | val = not_gate(val) 159 | for i in range (1, len(in_edge_list)): 160 | if(gate_type == 'or'): 161 | val = or_gate(val, in_edge_list[i][2]['value']) 162 | elif(gate_type == 'and'): 163 | val = and_gate(val, in_edge_list[i][2]['value']) 164 | elif(gate_type == 'nand'): 165 | val = nand_gate(val, in_edge_list[i][2]['value']) 166 | elif(gate_type == 'nor'): 167 | val = nor_gate(val, in_edge_list[i][2]['value']) 168 | elif(gate_type == 'xnor'): 169 | val = xnor_gate(val, in_edge_list[i][2]['value']) 170 | elif(gate_type == 'xor'): 171 | val = xor_gate(val, in_edge_list[i][2]['value']) 172 | 173 | else: 174 | val = 'x' 175 | return val 176 | 177 | "This is very helpful function. Gets the node whose number is given by num" 178 | def get_node(G, num): 179 | node_iter = G.nodes_iter(True) 180 | for iter in node_iter: 181 | if (iter[0] == num): 182 | return iter 183 | return 184 | 185 | "Takes an edge and its value as input and finds the implications of this value on entire graph and modifies the edge values accordingly" 186 | def imply_edge(G, edge, val, implications): 187 | if(edge[2]['fault'] == 'none'): 188 | edge[2]['value'] = val 189 | elif(edge[2]['fault'] == 'sa1'): 190 | edge[2]['value'] = output_val(val, '1') 191 | else: 192 | edge[2]['value'] = output_val(val, '0') 193 | 194 | if edge not in implications[implications.last()[0]]: 195 | implications[implications.last()[0]].append(edge) 196 | 197 | node_iter = G.nodes_iter(True) 198 | out_node = get_node(G, edge[1]) 199 | out_edge_list = G.out_edges(edge[1], True) 200 | 201 | if(out_node[1]['type'] == 'output'): 202 | return 203 | if(out_node[1]['type'] == 'gate'): 204 | out_val = calc_output(G, out_node) 205 | 206 | if(out_val != 'x'): 207 | for out_edge in out_edge_list: 208 | imply_edge(G, out_edge, out_val, implications) 209 | return 210 | return 211 | 212 | 213 | "This is the function to which call needs to be made in the main program. pi here stands for primary input. val stands for its new value ('0' or '1')" 214 | def imply(G, pi, val, implications): 215 | out_edge_list = G.out_edges(pi, True) 216 | for out_edge in out_edge_list: 217 | imply_edge(G, out_edge, val, implications) 218 | return 219 | 220 | def NOT(value): 221 | if (value == 0): 222 | return 1 223 | elif (value == 1): 224 | return 0 225 | 226 | def ctrl_value(type): 227 | if (type == 'and' or type == 'nand'): 228 | return 0 229 | elif (type == 'or' or type == 'nor'): 230 | return 1 231 | else: 232 | return -1 233 | "-1 represents that a controlling value is not defined" 234 | 235 | def get_po_list(G): 236 | l = [] 237 | attr_list = nx.get_node_attributes(G, 'type') 238 | for key in attr_list.keys(): 239 | if attr_list[key] == 'output': 240 | l.append(key) 241 | return l 242 | 243 | def has_fault_propagated(G, po_list): 244 | for item in G.in_edges_iter(po_list, True): 245 | if item[2]['value'] == 'D' or item[2]['value'] == 'D_bar': 246 | return True 247 | return False 248 | 249 | def get_d_frontier(G): 250 | l = [] 251 | flag1 = 0 252 | flag2 = 0 253 | gate_attr_list = nx.get_node_attributes(G, 'type') 254 | edge_attr_list = nx.get_edge_attributes(G, 'value') 255 | for key1 in gate_attr_list.keys(): 256 | flag1 = 0 257 | flag2 = 0 258 | "For every node which is a gate" 259 | if gate_attr_list[key1] == 'gate': 260 | for key2 in edge_attr_list.keys(): 261 | if (key2[0] == key1): 262 | "Checking if any of the output of the node is 'x'" 263 | if (edge_attr_list[key2] == 'x'): 264 | flag1 = 1 265 | for key2 in edge_attr_list.keys(): 266 | if (key2[1] == key1): 267 | "And checking if at least one of the input is D or D_bar" 268 | if (edge_attr_list[key2] == 'D' or edge_attr_list[key2] == 'D_bar'): 269 | flag2 = 1 270 | "If both conditions are satisfied, add this gate to the list of D-frontier" 271 | if((flag1 * flag2) == 1): 272 | l.append(key1) 273 | else: 274 | "Do nothing" 275 | return l 276 | 277 | def x_path_check(l): 278 | if not l: 279 | return False 280 | else: 281 | return True 282 | 283 | def IsUnassigned(G, node1, node2): 284 | for key in nx.get_edge_attributes(G, 'value').keys(): 285 | if(key[0] == node1 and key[1] == node2): 286 | if (nx.get_edge_attributes(G, 'value')[key] == 'x'): 287 | return True 288 | else: 289 | "Do nothing" 290 | else: 291 | "Continue search" 292 | return False 293 | 294 | 295 | "returns a tuple (input_edge,value) where input_edge is a 2-tuple and value is a number" 296 | def Objective(G, node1, node2, fvalue): 297 | "fvalue should be True or False" 298 | if(IsUnassigned(G, node1, node2)): 299 | return (node1, node2, NOT(fvalue)) 300 | "Select a gate P from the D-frontier" 301 | D_frontier = get_d_frontier(G) 302 | node1 = D_frontier[0] 303 | for item in G.in_edges_iter(node1, 'x'): 304 | if (item[2]["value"] == 'x'): 305 | input_edge = (item[0], item[1]) 306 | break 307 | "If gate g has controlling value" 308 | "Else if 0 value easier to get at input of XOR/EQUIV gate" 309 | "Else c=0" 310 | if (ctrl_value(nx.get_node_attributes(G, 'gatetype')[node1]) != -1): 311 | c = ctrl_value(nx.get_node_attributes(G, 'gatetype')[node1]) 312 | elif ((G.node[node1]['type'] == 'xor' or G.node[node1]['type'] == 'xnor') and (G.node[item[0]]['cc0'] < G.node[item[0]]['cc1'])): 313 | c = 1 314 | else: 315 | c = 0 316 | 317 | return (input_edge, NOT(c)) 318 | 319 | 320 | "this function returns controllability values of node, where node is a number" 321 | def get_controllability(G, node): 322 | if G.node[node]['type'] == 'output' : 323 | for predecessors in G.predecessors_iter(node): 324 | l0 = G.node[predecessors]['cc0'] 325 | l1 = G.node[predecessors]['cc1'] 326 | G.node[node]['cc0'] = l0 327 | G.node[node]['cc1'] = l1 328 | else : 329 | for incoming in G.predecessors_iter(node): 330 | if G.node[incoming]['cc0'] == 1 and G.node[incoming]['cc1'] == 1 and G.node[incoming]['type'] != 'input': 331 | get_controllability(G, incoming) 332 | l0 = [] 333 | l1 = [] 334 | for predecessors in G.predecessors_iter(node): 335 | l0.append(G.node[predecessors]['cc0']) 336 | l1.append(G.node[predecessors]['cc1']) 337 | 338 | if G.node[node]['gatetype'] == 'and': 339 | G.node[node]['cc0'] = min(l0) + 1 340 | G.node[node]['cc1'] = sum(l1) + 1 341 | 342 | elif G.node[node]['gatetype'] == 'nand': 343 | G.node[node]['cc0'] = sum(l1) + 1 344 | G.node[node]['cc1'] = min(l0) + 1 345 | 346 | elif G.node[node]['gatetype'] == 'or': 347 | G.node[node]['cc0'] = sum(l0) + 1 348 | G.node[node]['cc1'] = min(l1) + 1 349 | 350 | elif G.node[node]['gatetype'] == 'nor': 351 | G.node[node]['cc0'] = min(l1) + 1 352 | G.node[node]['cc1'] = sum(l0) + 1 353 | 354 | elif G.node[node]['gatetype'] == 'not': 355 | G.node[node]['cc0'] = sum(l1) + 1 356 | G.node[node]['cc1'] = sum(l0) + 1 357 | 358 | elif G.node[node]['gatetype'] == 'xor': 359 | l2 = [] 360 | l3 = [] 361 | i = 0 362 | for predecessors in G.predecessors_iter(node): 363 | if i % 2 == 0 : 364 | l2.append(G.node[predecessors]['cc0']) 365 | l3.append(G.node[predecessors]['cc1']) 366 | else : 367 | l2.append(G.node[predecessors]['cc1']) 368 | l3.append(G.node[predecessors]['cc0']) 369 | i += 1 370 | G.node[node]['cc0'] = min(sum(l0), sum(l1)) + 1 371 | G.node[node]['cc1'] = min(sum(l2), sum(l3)) + 1 372 | 373 | elif G.node[node]['gatetype'] == 'xnor': 374 | l2 = [] 375 | l3 = [] 376 | i = 0 377 | for predecessors in G.predecessors_iter(node): 378 | if i % 2 == 0 : 379 | l2.append(G.node[predecessors]['cc0']) 380 | l3.append(G.node[predecessors]['cc1']) 381 | else : 382 | l2.append(G.node[predecessors]['cc1']) 383 | l3.append(G.node[predecessors]['cc0']) 384 | i += 1 385 | G.node[node]['cc0'] = min(sum(l2), sum(l3)) + 1 386 | G.node[node]['cc1'] = min(sum(l0), sum(l1)) + 1 387 | 388 | 389 | "nodes_iter returns 2-tuple (node(nbr), data-dict)" 390 | def preprocess(G): 391 | for items in G.nodes_iter(data=True): 392 | if items[1]['type'] != 'input' and items[1]['cc0'] == 1 and items[1]['cc1'] == 1: 393 | get_controllability(G, items[0]) 394 | 395 | "checks if objective requires setting of all inputs" 396 | def all_inputs_req(G, input_edge, vs) : 397 | if G.node[input_edge[0]]['gatetype'] == 'and' and vs == 1 : 398 | return True 399 | elif G.node[input_edge[0]]['gatetype'] == 'or' and vs == 0 : 400 | return True 401 | elif G.node[input_edge[0]]['gatetype'] == 'nand' and vs == 0 : 402 | return True 403 | elif G.node[input_edge[0]]['gatetype'] == 'nor' and vs == 1 : 404 | return True 405 | elif G.node[input_edge[0]]['gatetype'] == 'not' : 406 | return True 407 | else : 408 | return False 409 | 410 | 411 | "input_edge is a 2-tuple of nodes given in (source, dest) manner, where source and dest are numbers" 412 | "takes in a tuple, returns list" 413 | def Backtrace(G, input_edge, vs, implications): 414 | v = vs 415 | new_input_edge = list(input_edge) 416 | "tuple to list conversion is done as only lists, but not tuples can be updated" 417 | while G.node[new_input_edge[0]]['type'] == 'gate' : 418 | if G.node[new_input_edge[0]]['gatetype'] == 'nand' or G.node[new_input_edge[0]]['gatetype'] == 'not' or G.node[new_input_edge[0]]['gatetype'] == 'nor' : 419 | v = NOT(v) 420 | ls = [x for x in G.in_edges(new_input_edge[0], data=True) if x[2]['value'] == 'x'] 421 | if all_inputs_req(G, new_input_edge, vs) : 422 | if v : 423 | edge = max(ls, key=lambda x:G.node[x[0]]['cc1']) 424 | else : 425 | edge = max(ls, key=lambda x:G.node[x[0]]['cc0']) 426 | else : 427 | if v : 428 | edge = min(ls, key=lambda x:G.node[x[0]]['cc1']) 429 | else : 430 | edge = min(ls, key=lambda x:G.node[x[0]]['cc0']) 431 | new_input_edge[0] = edge[0] 432 | "add the objective at input to the implication stack" 433 | a = (new_input_edge[0], v) 434 | if len(implications) == 0 : 435 | implications[a]=[] 436 | elif (a[0] != implications.last()[0][0]) or (a[1] != implications.last()[0][1]) : 437 | implications[a]=[] 438 | return a 439 | # objects returned are the primary input and its objective value 440 | 441 | "Backtrack returns a tuple (node,value), which is the new PI assignment OR an empty list" 442 | def Backtrack(G, implications) : 443 | if len(implications) == 0 : 444 | "do nothing" 445 | elif len(implications) == 1 : 446 | "remove all assignments due to the item implications[len(implications)-1], accessed from hash table" 447 | for edges in implications.last()[1] : 448 | edges[2]['value'] = 'x' 449 | 450 | item = (implications.last()[0][0],NOT(implications.last()[0][1])) 451 | implications[item]=[] 452 | elif implications.last()[0][0] == implications.lastbutone()[0][0] : 453 | "remove all assignments due to the item implications[len(implications)-1], accessed from hash table" 454 | for edges in implications.last()[1] : 455 | edges[2]['value'] = 'x' 456 | 457 | del implications[implications.last()[0]] 458 | del implications[implications.lastbutone()[0]] 459 | Backtrack(G, implications) 460 | else : 461 | item = (implications.last()[0][0],NOT(implications.last()[0][1])) 462 | "remove all assignments due to the item implications[len(implications)-1], accessed from hash table" 463 | for edges in implications.last()[1] : 464 | edges[2]['value'] = 'x' 465 | 466 | implications[item]=[] 467 | if not implications : 468 | return () 469 | else : 470 | return implications.last()[0] 471 | 472 | 473 | "PODEM algorithm" 474 | "fault is stuck-at-fvalue on the edge node1->node2, implications is the global implication stack : it is also the test vector" 475 | def podem_comb(G, implications, node1, node2, fvalue) : 476 | po_list = get_po_list(G) 477 | while not has_fault_propagated(G, po_list) : 478 | dfrontier = get_d_frontier(G) 479 | if x_path_check(dfrontier) : 480 | [l, vl] = Objective(G, node1, node2, fvalue) 481 | [pi, vpi] = Backtrace(G, l, vl, implications) 482 | "print [pi, vpi]" 483 | imply(G, pi, str(vpi),implications) 484 | if podem_comb(G, implications, node1, node2, fvalue) : 485 | return True 486 | result = Backtrack(G, implications) # Backtrack can return new PI assignmnet or an empty list 487 | if not result : # implications list has been exhausted 488 | return False 489 | else : 490 | pi = result[0] 491 | vpi = result[1] 492 | imply(G, pi, str(vpi),implications) 493 | if podem_comb(G, implications, node1, node2, fvalue) : 494 | return True 495 | imply(G, pi, 'x',implications) 496 | return False 497 | elif not implications : 498 | return False 499 | else : 500 | result = Backtrack(G, implications) 501 | return True 502 | 503 | 504 | def fault_assign(G, node1, node2, fvalue) : 505 | if fvalue : 506 | G[node1][node2]['value'] = 'D_bar' 507 | else : 508 | G[node1][node2]['value'] = 'D' 509 | 510 | 511 | G = nx.DiGraph() 512 | G.add_nodes_from([1, 2, 3], type='input', cc0=1, cc1=1) 513 | G.add_node(4, type='gate', gatetype='and', cc0=1, cc1=1) 514 | G.add_nodes_from([5, 6, 7], type='gate', gatetype='xnor', cc0=1, cc1=1) 515 | G.add_nodes_from([8, 9], type='gate', gatetype='not', cc0=1, cc1=1) 516 | G.add_nodes_from([10, 11], type='gate', gatetype='nand', cc0=1, cc1=1) 517 | G.add_node(12, type='gate', gatetype='or', cc0=1, cc1=1) 518 | G.add_nodes_from([13, 14, 15], type='output', cc0=1, cc1=1) 519 | G.add_edges_from([(1, 4), (2, 4), (2, 6), (2, 7), (3, 11), (4, 10), (4, 5), (4, 6), (5, 8), (5, 10), (6, 5), (6, 9), (6, 11), (7, 11), (8, 7), (9, 12), (10, 13), (11, 15), (12, 14)], value='x', fault='none') 520 | G.add_edge(8, 12, value='x', fault='sa1') 521 | nx.draw_circular(G) 522 | plt.savefig("samplegraph.png") 523 | plt.ion() 524 | plt.show() 525 | preprocess(G) # preprocess assigns all controllability values to nodes 526 | 527 | 528 | "fault_assign(G,8,12,1)" 529 | "test for podem_comb" 530 | "implications is a derived subclass of OrderedDict which stores PI assignments with corresponding implications," 531 | "It also stores keys in the order in which they were added" 532 | implications = MyOrderedDict() 533 | fault_assign(G, 8, 12, 1) 534 | fault_assign(G, 8, 7, 1) 535 | 536 | a = podem_comb(G, implications, 8, 12, 1) 537 | print '***' 538 | print a 539 | print implications 540 | --------------------------------------------------------------------------------