import re
import sys
import argparse
from collections import defaultdict
[docs]
def read_file(fn):
with open(fn) as f:
return f.read().splitlines()
[docs]
def write_file(fn, lines):
with open(fn, 'w') as f:
f.write('\n'.join(lines)+'\n')
[docs]
def debug_dump_head(lines, n=10):
print(f"=== first {n} lines of the file ===")
for i,l in enumerate(lines[:n]):
print(f"{i+1:2d}: {l!r}")
print("==================================\n")
[docs]
def find_layers(lines):
"""Return sorted list of layer‐IDs found in any wire or instance name."""
layer_set = set()
pat = re.compile(r'_layer_(\d+)_')
for l in lines:
for m in pat.finditer(l):
layer_set.add(int(m.group(1)))
return sorted(layer_set)
[docs]
def collect_wires(lines):
"""Return dict wire_name -> declaration line"""
wires = {}
pat = re.compile(r'^\s*wire\s+([^;]+);\s*$')
for l in lines:
m = pat.match(l)
if m:
name = m.group(1).strip().split()[-1]
wires[name] = l
# debug print
print(f"Collected {len(wires)} wire declarations (showing up to 5):")
for i,(nm,decl) in enumerate(wires.items()):
if i>=5: break
print(f" {nm:40s} -> {decl}")
print()
return wires
[docs]
def collect_instances(lines):
"""
Scan for ANY instantiation that contains '_layer_<N>_'
in its module or instance name, grab the entire block
from the line with the '(' down through the matching ');'
"""
insts = []
buf = []
paren_depth = 0
# match a line like " grid_io_top grid_io_top_1_9_layer_0_ ("
start_pat = re.compile(r'^\s*\w+\s+\w+.*_layer_\d+_.*\(')
for l in lines:
if not buf:
# are we at the start of a layer‐qualified instance?
if start_pat.match(l):
buf = [l]
# compute initial parenthesis depth
paren_depth = l.count('(') - l.count(')')
# if it closed on the same line, flush it immediately
if paren_depth == 0:
insts.append(''.join(buf))
buf = []
else:
# we are inside an inst block; keep accumulating
buf.append(l)
paren_depth += l.count('(') - l.count(')')
# once we've balanced all parentheses, that's the end
if paren_depth == 0:
insts.append(''.join(buf))
buf = []
# debug
print(f"Collected {len(insts)} layer‐qualified instantiations.")
if insts:
print("=== sample instance ===")
print(insts[0])
print("=======================\n")
return insts
[docs]
def layer_of_name(name):
"""Return layer id embedded in name, or None."""
m = re.search(r'_layer_(\d+)_', name)
return int(m.group(1)) if m else None
[docs]
def group_wires_by_layer(wires):
by_layer = defaultdict(dict)
for nm, decl in wires.items():
L = layer_of_name(nm)
if L is not None:
by_layer[L][nm] = decl
return by_layer
[docs]
def group_insts_by_layer(instances):
by_layer = defaultdict(list)
for inst in instances:
# look for any *_layer_N_ in the instance text
Ls = set(int(m) for m in re.findall(r'_layer_(\d+)_ \(', inst))
if len(Ls)==1:
by_layer[Ls.pop()].append(inst)
else:
# ambiguous or global; assign to None
by_layer[None].append(inst)
return by_layer
[docs]
def find_cross_wires(insts_by_layer):
"""
Build a map net -> set(layers) by scanning all '(net)' occurrences
in each instance. A net is cross-layer if it shows up in >=2 layers.
"""
usage = defaultdict(set)
cross_sizes = defaultdict()
# find every (...) and grab the inside as a net name
pat = re.compile(r'\s*\w+\((\w+)(\[\d+:\d+\])?\s*')
for layer, inst_list in insts_by_layer.items():
for inst in inst_list:
for m in pat.finditer(inst):
net = m.group(1)
size = m.group(2) or '[0:0]'
usage[net].add(layer)
if net not in cross_sizes.keys():
cross_sizes[net] = size
else:
size_regex = re.compile(r'\[(\d+):(\d+)\]')
found_size = size_regex.match(cross_sizes[net])
new_size = size_regex.match(size)
if found_size and new_size:
size_str = '['
# check if the sizes are the same
if int(found_size.group(1)) > int(new_size.group(1)):
size_str += new_size.group(1) + ':'
else:
size_str += found_size.group(1) + ':'
if int(found_size.group(2)) < int(new_size.group(2)):
size_str += new_size.group(2) + ']'
else:
size_str += found_size.group(2) + ']'
cross_sizes[net] = size_str
# debug: print the first few nets and their layer‐usage
print(f"Total distinct nets seen in instances: {len(usage)}")
for i,(net,lset) in enumerate(usage.items()):
if i >= 10: break
print(f" {net:30s} with size: {cross_sizes[net]} used in layers {sorted(lset)}")
print()
# now pick nets that appear in 2+ real layers
cross = { net:[L for L in lset if L is not None] for net,lset in usage.items()
if len({L for L in lset if L is not None}) > 1 }
# Keep only the cross sizes for the cross wires
cross_sizes_ret = { net: cross_sizes[net] for net in cross if net in cross_sizes.keys() }
# debug print first few cross wires and their sizes
print(f"Found {len(cross_sizes_ret)} cross-layer wires (showing up to 10):")
for i,(net,size) in enumerate(cross_sizes_ret.items()):
if i >= 5: break
print(f" {net:30s} with size: {size}")
# print first 5 elements of cross
print(f"Found {len(cross)} cross-layer wires (showing up to 5):")
for i,net in enumerate(cross):
if i >= 5: break
print(" ", net, "layers=", sorted(usage[net]))
print()
# print(f"Found {len(cross)} cross-layer wires (showing up to 10):")
# for net in sorted(cross)[:10]:
# print(" ", net, "layers=", sorted(usage[net]))
# print()
return cross, cross_sizes_ret
[docs]
def make_submodule(layer, wires, instances, cross_wires, cross_sizes, global_ports):
"""
Generate the text of fpga_layer_{layer}.v
- global_ports is a list of (direction, name, width) from the original top module
"""
name = f"fpga_layer_{layer}"
lines = []
# ports:
# * all global_ports (pReset, prog_clk, ...) always passed down
# * all cross_wires that belong to this layer:
ports = []
for direction,name0,width in global_ports:
ports.append(f"{direction} {width} {name0}")
# add cross-layer wires as ports
for w, wire_layers in cross.items():
if layer in wire_layers:
# Default to "inout" for now
wire_direction = "inout"
if f"layer_{layer}_" in w:
# this wire is local to this layer, so it's an output to another layer
wire_direction = "output"
else:
# this wire is cross-layer, so it's an input to this layer
wire_direction = "input"
ports.append(f"{wire_direction} {cross_sizes[w]} {w}")
# module header
pl = ',\n '.join(ports)
lines.append(f"module {name}(\n {pl}\n);")
lines.append("")
# internal wires = all wires assigned to this layer minus cross
for w,decl in wires.items():
if w not in cross_wires:
lines.append(" " + decl)
lines.append("")
# instances
for inst in instances:
lines.append(" " + inst.replace("\n", "\n "))
lines.append("")
lines.append(f"endmodule // {name}")
return lines
if __name__=="__main__":
import argparse, os
from collections import defaultdict
p = argparse.ArgumentParser()
p.add_argument("-i","--input", required=True, help="fpga_top.v")
p.add_argument("-o","--output_dir", default="layers", help="where to put split files")
args = p.parse_args()
# read
lines = open(args.input).read().splitlines()
# debug: make sure we see your actual text
# debug_dump_head(lines, n=50)
# extract ports & wires
global_ports = extract_global_ports(lines)
wires_all = collect_wires(lines)
# rest of your logic follows…
layers = find_layers(lines)
print("Detected layers:", layers, "\n")
wires_by_layer = group_wires_by_layer(wires_all)
instances = collect_instances(lines)
insts_by_layer = group_insts_by_layer(instances)
# print("=== sample instance for layer 0 ===")
# print(insts_by_layer[0][0])
# print("===================================\n")
# debug #1: print how many instances in each bucket
for L, inst_list in insts_by_layer.items():
print(f"Layer {L!r} has {len(inst_list)} instances")
cross, cross_sizes = find_cross_wires(insts_by_layer)
# print("Cross-layer wires:", sorted(cross), "\n")
for _, portname, _ in global_ports:
if portname in cross.keys():
del cross[portname] # remove global ports from cross wires
del cross_sizes[portname]
wire_to_layers = defaultdict(set)
for L, wdict in wires_by_layer.items():
for w in wdict:
wire_to_layers[w].add(L)
os.makedirs(args.output_dir, exist_ok=True)
# emit layer modules
for L in layers:
sub = make_submodule(L, wires_by_layer[L], insts_by_layer.get(L,[]),
cross, cross_sizes, global_ports)
fn = os.path.join(args.output_dir, f"fpga_layer_{L}.v")
write_file(fn, sub)
print("Wrote", fn)
# emit new top
top = [f"module fpga_top({','.join([pname for _, pname, _ in global_ports])});",""]
for global_port in global_ports:
direction, pname, width = global_port
top.append(f" {direction} {width} {pname};")
top.append("")
for w in sorted(cross.keys()):
top.append(" " + wires_all[w]) if w in wires_all else top.append(f" wire {w};")
top.append("")
for L in layers:
name = f"fpga_layer_{L}"
inst = [f" {name} U_layer_{L}("]
conns = []
for direction,pname,width in global_ports:
conns.append(f".{pname}({pname})")
for w, layers in cross.items():
if L in layers:
conns.append(f".{w}({w})")
inst.append(" " + ",\n ".join(conns))
inst.append(" );\n")
top.extend(inst)
top.append("endmodule")
write_file(os.path.join(args.output_dir,"fpga_top_split.v"), top)
print("Wrote fpga_top_split.v in", args.output_dir)