From e7cc1c01686dddc0a783c0abdc921fc562a97a8d Mon Sep 17 00:00:00 2001 From: Nick Downing Date: Sat, 19 Jul 2025 12:05:02 +1000 Subject: [PATCH] Change format of nodes.txt file to have the counts in the header line, code cleanup in /scripts/nodes.py which generates the file, change format of channels.txt file to contain the block, coordinates and mass (capacitance) and improve diff_nets/r_matrix, code cleanup in /scripts/channels.py which generates the file --- 8085/Makefile | 11 ++- scripts/channels.py | 214 ++++++++++++++++++++++++------------------ scripts/node_image.py | 17 +--- scripts/node_sizes.py | 6 +- scripts/nodes.py | 58 ++++++++---- 5 files changed, 173 insertions(+), 133 deletions(-) diff --git a/8085/Makefile b/8085/Makefile index 184f987..b6e0938 100644 --- a/8085/Makefile +++ b/8085/Makefile @@ -26,16 +26,16 @@ layers_rev.png #vias2.png channels.txt: nodes.txt - ../scripts/channels.py nodes.txt 4,5,6,7 >$@ + ../scripts/channels.py nodes.txt 1,4,5,6,7 >$@ node_sizes.txt: nodes.txt ../scripts/node_sizes.py nodes.txt >$@ node_gnd.png: nodes.txt - ../scripts/node_image.py --adjacency nodes.txt 2_2507_0147 $@ ${COLOURS} + ../scripts/node_image.py --adjacency nodes.txt pad_GND $@ ${COLOURS} node_vcc.png: nodes.txt - ../scripts/node_image.py --adjacency nodes.txt 2_3232_6986 $@ ${COLOURS} + ../scripts/node_image.py --adjacency nodes.txt pad_VCC $@ ${COLOURS} node_phi0.png: nodes.txt ../scripts/node_image.py --adjacency nodes.txt 3_0455_4632 $@ ${COLOURS} @@ -50,8 +50,9 @@ metal.png \ poly.png \ split_diff.png \ vias.png \ -conn_matrix.txt - ../scripts/nodes.py blocks.png,pads.png,metal.png,poly.png,split_diff.png,vias.png conn_matrix.txt $@ +conn_matrix.txt \ +node_names.txt + ../scripts/nodes.py blocks.png,pads.png,metal.png,poly.png,split_diff.png,vias.png conn_matrix.txt node_names.txt $@ # this one follows same layering as nodes.txt and we can't see pads layer layers.png: \ diff --git a/scripts/channels.py b/scripts/channels.py index d293345..5a256ad 100755 --- a/scripts/channels.py +++ b/scripts/channels.py @@ -19,43 +19,36 @@ import sys if len(sys.argv) < 3: print( - f'usage: {sys.argv[0]:s} nodes.txt poly_colour,diff_colour,buried_colour,channel_colour' + f'usage: {sys.argv[0]:s} nodes.txt blocks_colour,poly_colour,diff_colour,buried_colour,channel_colour' ) sys.exit(1) nodes_txt = sys.argv[1] -poly_colour, diff_colour, buried_colour, channel_colour = \ +blocks_colour, poly_colour, diff_colour, buried_colour, channel_colour = \ [int(i) for i in sys.argv[2].split(',')] diff_colours = {diff_colour, buried_colour} with open(nodes_txt) as fin: line = fin.readline() assert len(line) - _, ys, xs = [int(i) for i in line.split()] - - line = fin.readline() - assert len(line) - n_items = int(line.rstrip()) + _, ys, xs, n_items, n_reports = [int(i) for i in line.split()] + channels = {} items = [] - channel_items = {} for i in range(n_items): line = fin.readline() assert len(line) fields = line.split() + assert len(fields) == 5 node = fields[0] colour = int(fields[1]) - y = int(fields[2]) + y0 = int(fields[2]) x0 = int(fields[3]) x1 = int(fields[4]) if colour == channel_colour: - if node not in channel_items: - channel_items[node] = ([], []) # items, reports (report may be swapped) - channel_items[node][0].append(len(items)) - items.append((node, colour, y, x0, x1)) - - line = fin.readline() - assert len(line) - n_reports = int(line.rstrip()) + if node not in channels: + channels[node] = ([], []) # items, reports (report may be swapped) + channels[node][0].append(len(items)) + items.append((node, colour, y0, x0, x1)) for i in range(n_reports): line = fin.readline() @@ -63,10 +56,10 @@ with open(nodes_txt) as fin: item0, item1 = [int(i) for i in line.split()] node0 = items[item0][0] node1 = items[item1][0] - if node0 in channel_items: - channel_items[node0][1].append((item0, item1)) - if node1 in channel_items: - channel_items[node1][1].append((item1, item0)) + if node0 in channels: + channels[node0][1].append((item0, item1)) + if node1 in channels: + channels[node1][1].append((item1, item0)) def resistor_solver(image): ys, xs = image.shape @@ -166,79 +159,114 @@ def resistor_solver(image): return 1. / sum_I2 image = numpy.zeros((ys, xs), numpy.uint8) -for ( - channel_node, - (channel_items, channel_reports) -) in sorted(channel_items.items()): +for (channel, (channel_items, channel_reports)) in sorted(channels.items()): #adjacent_nodes = set([items[item1][0] for _, item1 in channel_reports]) #adjacent_colours = set([items[item1][1] for _, item1 in channel_reports]) - #print('channel_node', channel_node, 'adjacent_nodes', adjacent_nodes, 'adjacent_colours', adjacent_colours) - - poly_nodes = set( - [ - items[item1][0] - for _, item1 in channel_reports - if items[item1][1] == poly_colour - ] + #print('channel', channel, 'adjacent_nodes', adjacent_nodes, 'adjacent_colours', adjacent_colours) + + # find the block this channel belongs to + blocks = sorted( + set( + [ + items[item1][0] + for _, item1 in channel_reports + if items[item1][1] == blocks_colour + ] + ) + ) + + # find adjacent nets by connection type + poly_nets = sorted( + set( + [ + items[item1][0] + for _, item1 in channel_reports + if items[item1][1] == poly_colour + ] + ) ) - diff_nodes = set( - [ - items[item1][0] - for _, item1 in channel_reports - if items[item1][1] in diff_colours - ] + diff_nets = sorted( + set( + [ + items[item1][0] + for _, item1 in channel_reports + if items[item1][1] in diff_colours + ] + ) ) - if len(poly_nodes) == 1 and len(diff_nodes) == 2: - # find channel resistance by creating image for resistor solver - # conductor is filled in colour 3, then electrodes in colours 1 and 2 - # bounding box is tracked in order to pass the smallest possible image - # into the solver, and to clear this region from the large image after - min_x = xs - max_x = 0 - min_y = ys - max_y = 0 - for item in channel_items: - _, _, y, x0, x1 = items[item] - if x0 < min_x: - min_x = x0 - if x1 > max_x: - max_x = x1 - if y < min_y: - min_y = y - if y + 1 > max_y: - max_y = y + 1 - image[y, x0:x1] = 3 # white (conductor) - sink_node, source_node = tuple(diff_nodes) - electrodes = {sink_node: 1, source_node: 2} - for item0, item1 in channel_reports: - node, colour, y1, x2, x3 = items[item1] - if colour in diff_colours: - # trim neighbouring item to only include direct neighbouring pixels - _, _, y0, x0, x1 = items[item0] - if x2 < x0 - 1: - x2 = x0 - 1 - if x3 > x1 + 1: - x3 = x1 + 1 - - if x2 < min_x: - min_x = x2 - if x3 > max_x: - max_x = x3 - if y1 < min_y: - min_y = y1 - if y1 + 1 > max_y: - max_y = y1 + 1 - image[y1, x2:x3] = electrodes[node] - - # diagnostics (usually commented), need to make directory first - #image_pil = PIL.Image.new('P', (max_x - min_x, max_y - min_y), None) - #image_pil.frombytes(image[min_y:max_y, min_x:max_x].tobytes()) - #image_pil.putpalette(list(PALETTE.reshape((12,)))) - #image_pil.save(f'channels/{channel_node:d}.png') - - R = resistor_solver(image[min_y:max_y, min_x:max_x]) - image[min_y:max_y, min_x:max_x] = 0 - - print('channel_node', channel_node, 'poly_nodes', poly_nodes, 'diff_nodes', diff_nodes, 'R', R) - else: - print('channel_node', channel_node, 'poly_nodes', poly_nodes, 'diff_nodes', diff_nodes) + + # there must be exactly one each of block and poly nodes by construction + # (if this fails we need to OR the channels.png onto blocks.png / poly.png) + [block] = blocks + [poly_net] = poly_nets + + # find mass (capacitance) and centre of mass (coordinates) of the channel + mass = 0 + x = 0 + y = 0 + for item in channel_items: + _, _, y0, x0, x1 = items[item] + width = x1 - x0 + mass += width + x += (x0 + x1) * width + y += (2 * y0 + 1) * width + x /= 2 * mass + y /= 2 * mass + + # find the pairwise resistances between the sources/drains (none if < 2) + r_matrix = [] + for j in range(len(diff_nets)): + r_matrix.append([]) + for i in range(j): + # find channel resistance by creating image for resistor solver + # conductor is filled in colour 3, then electrodes in colours 1 and 2 + # bounding box is tracked in order to pass the smallest possible image + # into the solver, and to clear this region from the large image after + min_x = xs + max_x = 0 + min_y = ys + max_y = 0 + for item in channel_items: + _, _, y0, x0, x1 = items[item] + if x0 < min_x: + min_x = x0 + if x1 > max_x: + max_x = x1 + if y0 < min_y: + min_y = y0 + if y0 + 1 > max_y: + max_y = y0 + 1 + image[y0, x0:x1] = 3 # white (conductor) + electrodes = {diff_nets[i]: 1, diff_nets[j]: 2} + for item0, item1 in channel_reports: + net, colour, y1, x2, x3 = items[item1] + if colour in diff_colours: + # trim neighbouring item to only include direct neighbouring pixels + _, _, y0, x0, x1 = items[item0] + if x2 < x0 - 1: + x2 = x0 - 1 + if x3 > x1 + 1: + x3 = x1 + 1 + + if x2 < min_x: + min_x = x2 + if x3 > max_x: + max_x = x3 + if y1 < min_y: + min_y = y1 + if y1 + 1 > max_y: + max_y = y1 + 1 + image[y1, x2:x3] = electrodes[net] + + # diagnostics (usually commented), need to make directory manually + #image_pil = PIL.Image.new('P', (max_x - min_x, max_y - min_y), None) + #image_pil.frombytes(image[min_y:max_y, min_x:max_x].tobytes()) + #image_pil.putpalette(list(PALETTE.reshape((12,)))) + #image_pil.save(f'channels/{channel_net:d}_{i:d}_{j:d}.png') + + r_matrix[-1].append(resistor_solver(image[min_y:max_y, min_x:max_x])) + image[min_y:max_y, min_x:max_x] = 0 + + print(channel, block, poly_net, len(diff_nets), x, y, mass) + for i in range(len(diff_nets)): + print(diff_nets[i], ' '.join([str(j) for j in r_matrix[i]])) diff --git a/scripts/node_image.py b/scripts/node_image.py index 9f2aba1..4de78ce 100755 --- a/scripts/node_image.py +++ b/scripts/node_image.py @@ -41,32 +41,25 @@ if len(sys.argv) >= 5 else with open(nodes_txt) as fin: line = fin.readline() assert len(line) - n_colours, ys, xs = [int(i) for i in line.split()] + n_colours, ys, xs, n_items, n_reports = [int(i) for i in line.split()] image = numpy.zeros((ys, xs), numpy.uint8) - line = fin.readline() - assert len(line) - n_items = int(line.rstrip()) - items = [] for i in range(n_items): line = fin.readline() assert len(line) fields = line.split() + assert len(fields) == 5 node1 = fields[0] colour = int(fields[1]) - y = int(fields[2]) + y0 = int(fields[2]) x0 = int(fields[3]) x1 = int(fields[4]) - items.append((node1, colour, y, x0, x1)) + items.append((node1, colour, y0, x0, x1)) if node1 == node: - image[y, x0:x1] = colour + image[y0, x0:x1] = colour if adjacency: - line = fin.readline() - assert len(line) - n_reports = int(line.rstrip()) - adjacent_nodes = set() adjacent_colours = set() for i in range(n_reports): diff --git a/scripts/node_sizes.py b/scripts/node_sizes.py index aac213b..b04c4b0 100755 --- a/scripts/node_sizes.py +++ b/scripts/node_sizes.py @@ -14,15 +14,13 @@ node_sizes = {} with open(nodes_txt) as fin: line = fin.readline() assert len(line) - - line = fin.readline() - assert len(line) - n_items = int(line.rstrip()) + _, _, _, n_items, _= [int(i) for i in line.split()] for i in range(n_items): line = fin.readline() assert len(line) fields = line.split() + assert len(fields) == 5 node = fields[0] colour = int(fields[1]) y = int(fields[2]) diff --git a/scripts/nodes.py b/scripts/nodes.py index f4f4af7..7e7db88 100755 --- a/scripts/nodes.py +++ b/scripts/nodes.py @@ -11,14 +11,15 @@ CONN_ABORT = 3 PIL.Image.warnings.simplefilter('ignore', PIL.Image.DecompressionBombWarning) -if len(sys.argv) < 4: +if len(sys.argv) < 5: print( - f'usage: {sys.argv[0]:s} image_in,... conn_matrix.txt nodes.txt' + f'usage: {sys.argv[0]:s} image_in,... conn_matrix.txt node_names.txt nodes.txt' ) sys.exit(1) images_in = sys.argv[1].split(',') conn_matrix_txt = sys.argv[2] -nodes_txt = sys.argv[3] +node_names_txt = sys.argv[3] +nodes_txt = sys.argv[4] images = [] colours = [1] @@ -67,6 +68,24 @@ with open(conn_matrix_txt) as fin: colour += 1 line = fin.readline() +node_names = {} +node_names_used = set() +with open(node_names_txt) as fin: + line = fin.readline() + while len(line): + i = line.find('#') + if i != -1: + line = line[:i] + fields = line.split() + if len(fields): + #print('xxx', fields) + assert len(fields) == 2 + assert fields[0] not in node_names + assert fields[1] not in node_names_used + node_names[fields[0]] = fields[1] + node_names_used.add(fields[1]) + line = fin.readline() + ptrs = [] items = [] for i in range(len(images)): @@ -81,14 +100,14 @@ for i in range(len(images)): nz_y, nz_x = numpy.nonzero(image_delta) for j in range(0, nz_y.shape[0] - 1): - y = nz_y[j] + y0 = nz_y[j] x0 = nz_x[j] - colour = image_padded[y, x0] + colour = image_padded[y0, x0] if colour: x1 = nz_x[j + 1] - while len(ptrs) <= i * ys + y: + while len(ptrs) <= i * ys + y0: ptrs.append(len(items)) - items.append([len(items), colour, y, x0, x1]) + items.append([len(items), colour, y0, x0, x1]) while len(ptrs) <= len(images) * ys: ptrs.append(len(items)) @@ -124,10 +143,10 @@ def merge(item0, item1): def end_to_end(p, q): if q > p + 1: i = p - _, colour0, y, x0, x1 = items[i] + _, colour0, y0, x0, x1 = items[i] i += 1 while i < q: - _, colour1, _, x2, x3 = items[i] + _, colour1, y2, x2, x3 = items[i] if x1 == x2: conn = conn_matrix[colour0, colour1] if conn == CONN_NONE: @@ -137,8 +156,8 @@ def end_to_end(p, q): elif conn == CONN_REPORT: reports.append((i - 1, i)) else: - print('colour0', colour0, 'y', y, 'x0', x0, 'x1', x1) - print('colour1', colour1, 'y', y, 'x2', x2, 'x3', x3) + print('colour0', colour0, 'y0', y0, 'x0', x0, 'x1', x1) + print('colour1', colour1, 'y2', y2, 'x2', x2, 'x3', x3) assert False i += 1 colour0 = colour1 @@ -149,7 +168,7 @@ def touch(p, q, r, s): i = p _, colour0, y0, x0, x1 = items[i] j = r - _, colour1, y1, x2, x3 = items[j] + _, colour1, y2, x2, x3 = items[j] while True: if x2 >= x1: i += 1 @@ -171,7 +190,7 @@ def touch(p, q, r, s): reports.append((i, j)) else: print('colour0', colour0, 'y0', y0, 'x0', x0, 'x1', x1) - print('colour1', colour1, 'y1', y1, 'x2', x2, 'x3', x3) + print('colour1', colour1, 'y2', y2, 'x2', x2, 'x3', x3) assert False if x1 < x3: @@ -215,16 +234,15 @@ for i in range(len(images)): print('output') with open(nodes_txt, 'w') as fout: - print(colours[-1], ys, xs, file = fout) - print(len(items), file = fout) + print(colours[-1], ys, xs, len(items), len(reports), file = fout) for i in range(len(items)): node_item, colour0, y0, x0, x1 = items[i] - + j = items[node_item][0] while j != node_item: node_item = j j = items[node_item][0] - + j = i while j != node_item: k = items[j][0] @@ -233,9 +251,11 @@ with open(nodes_txt, 'w') as fout: _, colour1, y1, x2, _ = items[node_item] node = f'{colour1:d}_{y1:04d}_{x2:04d}' + if node in node_names: + node = node_names[node] + else: + assert node not in node_names_used print(node, colour0, y0, x0, x1, file = fout) - - print(len(reports), file = fout) for item0, item1 in reports: print(item0, item1, file = fout) -- 2.34.1