def mux(w): """ Create a mux. Parameters ---------- w: the width of the mux. Returns ------- a `CircuitGraph` mux. """ c = Circuit(name="mux") # create inputs for i in range(w): c.add(f"in_{i}", "input") sels = [] for i in range(clog2(w)): c.add(f"sel_{i}", "input") c.add(f"not_sel_{i}", "not", fanin=f"sel_{i}") sels.append([f"not_sel_{i}", f"sel_{i}"]) # create output or c.add("out", "or", output=True) i = 0 for sel in product(*sels[::-1]): c.add(f"and_{i}", "and", fanin=[*sel, f"in_{i}"], fanout="out") i += 1 if i == w: break return c
def adder(w): """ Create an adder. Parameters ---------- w : int Input width of adder. Returns ------- Circuit Adder circuit. """ c = Circuit(name="adder") carry = c.add("null", "0") for i in range(w): # sum c.add(f"a_{i}", "input") c.add(f"b_{i}", "input") c.add(f"sum_{i}", "xor", fanin=[f"a_{i}", f"b_{i}", carry]) c.add(f"out_{i}", "output", fanin=f"sum_{i}") # carry c.add(f"and_ab_{i}", "and", fanin=[f"a_{i}", f"b_{i}"]) c.add(f"and_ac_{i}", "and", fanin=[f"a_{i}", carry]) c.add(f"and_bc_{i}", "and", fanin=[f"b_{i}", carry]) carry = c.add( f"carry_{i}", "or", fanin=[f"and_ab_{i}", f"and_ac_{i}", f"and_bc_{i}"], ) c.add(f"out_{w}", "output", fanin=carry) return c
def __init__(self, text, blackboxes, warnings=False, error_on_warning=False): """ Initializes a new transformer. Parameters ---------- text: str The netlist that the transformer will be used on, used for error messages. blackboxes: list of circuitgraph.BlackBox The blackboxes present in the netlist that will be parsed. warnings: bool If True, warnings about unused nets will be printed. error_on_warning: bool If True, unused nets will cause raise `VerilogParsingWarning` exceptions. """ self.c = Circuit() self.text = text self.blackboxes = blackboxes self.warnings = warnings self.error_on_warning = error_on_warning self.tie0 = self.c.add("tie0", "0") self.tie1 = self.c.add("tie1", "1") self.io = set() self.inputs = set() self.outputs = set() self.wires = set()
def influence_transform(c, n, s): """ Creates a circuit to compute sensitivity. Parameters ---------- c : Circuit Sequential circuit to unroll. n : str Node to compute influence at. s : str Startpoint to compute influence for. Returns ------- Circuit Influence circuit. """ # check for blackboxes if c.blackboxes: raise ValueError(f"{c.name} contains a blackbox") # check if s is in startpoints sp = c.startpoints(n) if s not in sp: raise ValueError(f"{s} is not in startpoints of {n}") # get input cone fi_nodes = c.transitive_fanin(n) | set([n]) sub_c = Circuit("sub_cone", c.graph.subgraph(fi_nodes).copy()) # create two copies of sub circuit, share inputs except s infl = Circuit(name=f"infl_{s}_on_{n}") infl.add_subcircuit(sub_c, "c0") infl.add_subcircuit(sub_c, "c1") for g in sp: if g != s: infl.add(g, "input", fanout=[f"c0_{g}", f"c1_{g}"]) else: infl.add(f"not_{g}", "not", fanout=f"c1_{s}") infl.add(g, "input", fanout=[f"c0_{g}", f"not_{g}"]) infl.add("dif", "xor", fanin=[f"c0_{n}", f"c1_{n}"]) infl.add("sat", "output", fanin="dif") return infl
def bench_to_circuit(netlist, name): """ Creates a new Circuit from a netlist string. Parameters ---------- netlist: str netlist code. name: str the module name. Returns ------- Circuit the parsed circuit. """ # create circuit c = Circuit(name=name) # get inputs in_regex = r"(?:INPUT|input)\s*\(\s*([a-zA-Z][a-zA-Z\d_]*)\s*\)" for net_str in re.findall(in_regex, netlist, re.DOTALL): nets = net_str.replace(" ", "").replace("\n", "").replace("\t", "").split(",") for n in nets: c.add(n, "input") # handle gates regex = r"([a-zA-Z][a-zA-Z\d_]*)\s*=\s*(BUF|NOT|OR|NOR|AND|NAND|XOR|XNOR|buf|not|or|nor|and|nand|not|xor|xnor)\(([^\)]+)\)" for net, gate, input_str in re.findall(regex, netlist): # parse all nets inputs = (input_str.replace(" ", "").replace("\n", "").replace("\t", "").split(",")) c.add(net, gate.lower(), fanin=inputs) # get outputs in_regex = r"(?:OUTPUT|output)\s*\(\s*([a-zA-Z][a-zA-Z\d_]*)\s*\)" for net_str in re.findall(in_regex, netlist, re.DOTALL): nets = net_str.replace(" ", "").replace("\n", "").replace("\t", "").split(",") for n in nets: driver = c.uid(f"{n}_driver") c.relabel({n: driver}) c.add(n, "output", fanin=driver) return c
def miter(c0, c1=None, startpoints=None, endpoints=None): """ Creates a miter circuit Parameters ---------- c0 : Circuit First circuit. c1 : Circuit Optional second circuit, if None c0 is mitered with itself. startpoints : set of str Nodes to be tied together, must exist in both circuits. endpoints : set of str Nodes to be compared, must exist in both circuits. Returns ------- Circuit Miter circuit. """ # check for blackboxes if c0.blackboxes: raise ValueError(f"{c0.name} contains a blackbox") if c1 and c1.blackboxes: raise ValueError(f"{c1.name} contains a blackbox") # clean inputs if not c1: c1 = c0 if not startpoints: startpoints = c0.startpoints() & c1.startpoints() if not endpoints: endpoints = c0.endpoints() & c1.endpoints() # create miter, relabel m = Circuit(name=f"miter_{c0.name}_{c1.name}") m.add_subcircuit(c0, "c0") m.add_subcircuit(c1, "c1") # tie inputs for n in startpoints: m.add(n, "input", fanout=[f"c0_{n}", f"c1_{n}"]) # compare outputs m.add("miter", "or") m.add("sat", "output", fanin="miter") for n in endpoints: m.add(f"dif_{n}", "xor", fanin=[f"c0_{n}", f"c1_{n}"], fanout="miter") return m
def popcount(w): """ Create a population count circuit. Parameters ---------- w : int Input width of the circuit. Returns ------- Circuit Population count circuit. """ c = Circuit(name="popcount") ps = [[c.add(f"in_{i}", "input")] for i in range(w)] c.add("tie0", "0") i = 0 while len(ps) > 1: # get values ns = ps.pop(0) ms = ps.pop(0) # pad aw = max(len(ns), len(ms)) while len(ms) < aw: ms += ["tie0"] while len(ns) < aw: ns += ["tie0"] # instantiate and connect adder c.add_subcircuit(adder(aw), f"add_{i}") for j, (n, m) in enumerate(zip(ns, ms)): c.connect(n, f"add_{i}_a_{j}") c.connect(m, f"add_{i}_b_{j}") # add adder outputs ps.append([f"add_{i}_out_{j}" for j in range(aw + 1)]) i += 1 # connect outputs for i, o in enumerate(ps[0]): c.add(f"out_{i}", "output", fanin=o) if not c.fanout("tie0"): c.remove("tie0") return c
def popcount(w): """ Create a population count circuit. Parameters ---------- w: the width of the adder. Returns ------- a `CircuitGraph` addder. """ c = Circuit(name="popcount") ps = [[c.add(f"in_{i}", "input")] for i in range(w)] i = 0 while len(ps) > 1: # get values ns = ps.pop(0) ms = ps.pop(0) # pad aw = max(len(ns), len(ms)) while len(ms) < aw: ms += ["null"] while len(ns) < aw: ns += ["null"] # instantiate and connect adder a = adder(aw).strip_io() c.extend(a.relabel({n: f"add_{i}_{n}" for n in a.nodes()})) for j, (n, m) in enumerate(zip(ns, ms)): c.connect(n, f"add_{i}_a_{j}") c.connect(m, f"add_{i}_b_{j}") # add adder outputs ps.append([f"add_{i}_out_{j}" for j in range(aw + 1)]) i += 1 # connect outputs for i, o in enumerate(ps[0]): c.add(f"out_{i}", "buf", fanin=o, output=True) if "null" in c: c.set_type("null", "0") c.set_output("null", False) return c
def bench_to_circuit(bench, name): """ Creates a new Circuit from a bench string. Parameters ---------- bench: str bench code. name: str the module name. Returns ------- Circuit the parsed circuit. """ # create circuit c = Circuit(name=name) # get inputs in_regex = r"(?:INPUT|input)\s*\((.+?)\)" for net_str in re.findall(in_regex, bench, re.DOTALL): nets = net_str.replace(" ", "").replace("\n", "").replace("\t", "").split(",") for n in nets: c.add(n, "input") # handle gates regex = r"(\S+)\s*=\s*(NOT|OR|NOR|AND|NAND|XOR|XNOR|not|or|nor|and|nand|not|xor|xnor)\((.+?)\)" for net, gate, input_str in re.findall(regex, bench): # parse all nets inputs = (input_str.replace(" ", "").replace("\n", "").replace("\t", "").split(",")) # get outputs in_regex = r"(?:OUTPUT|output)\s*\((.+?)\)" for net_str in re.findall(in_regex, bench, re.DOTALL): nets = net_str.replace(" ", "").replace("\n", "").replace("\t", "").split(",") for n in nets: c.set_output(n) return c
def copy(c): """ Returns copy of a circuit. Parameters ---------- c : Circuit Input circuit. Returns ------- Circuit Circuit copy. """ return Circuit(graph=c.graph.copy(), name=c.name, blackboxes=c.blackboxes.copy())
def relabel(c, mapping): """ Builds copy with relabeled nodes. Parameters ---------- c : Circuit Input circuit. mapping : dict of str:str Relabeling of nodes. Returns ------- Circuit Circuit with removed blackboxes. """ g = nx.relabel_nodes(c.graph, mapping) return Circuit(graph=g, name=c.name, blackboxes=c.blackboxes.copy())
def xor_hash(n, m): """ Create a XOR hash function H_{xor}(n,m,3) as in: Chakraborty, Supratik, Kuldeep S. Meel, and Moshe Y. Vardi. "A scalable approximate model counter." International Conference on Principles and Practice of Constraint Programming. Springer, Berlin, Heidelberg, 2013. Each output of the hash is the xor/xnor of a random subset of the input. Parameters ---------- n : int Input width of the hash function. m : int Output width of the hash function. Returns ------- Circuit XOR hash function. """ h = Circuit() for i in range(n): h.add(f"in_{i}", "input") for o in range(m): h.add(f"out_{o}", "output") # select inputs cons = [random() < 0.5 for i in range(n)] if sum(cons) == 0: # constant output h.add(f"c_{o}", "1" if random() < 0.5 else "0", fanout=f"out_{o}") else: # choose between xor/xnor h.add(f"xor_{o}", "xor", fanout=f"out_{o}") h.add(f"c_{o}", "1" if random() < 0.5 else "0", fanout=f"xor_{o}") for i, con in enumerate(cons): if con: h.connect(f"in_{i}", f"xor_{o}") return h
def strip_blackboxes(c, ignore_pins=None): """ Converts blackboxes to io. Parameters ---------- c : Circuit Input circuit. ingnore_pins: str or list of str Pins to not create io for, just disconnect and delete. Returns ------- Circuit Circuit with removed blackboxes. """ if not ignore_pins: ignore_pins = [] elif isinstance(ignore_pins, str): ignore_pins = [ignore_pins] g = c.graph.copy() bb_pins = [] for n in c.filter_type("bb_input"): if n.split(".")[-1] in ignore_pins: g.remove_node(n) else: g.nodes[n]["type"] = "output" bb_pins.append(n) for n in c.filter_type("bb_output"): if n.split(".")[-1] in ignore_pins: g.remove_node(n) else: g.nodes[n]["type"] = "input" bb_pins.append(n) # rename nodes mapping = {n: n.replace(".", "_") for n in bb_pins} for k in mapping.values(): if k in g: raise ValueError(f"Overlapping blackbox name: {k}") nx.relabel_nodes(g, mapping, copy=False) return Circuit(graph=g, name=c.name)
def strip_inputs(c): """ Converts inputs to buffers for easy instantiation. Parameters ---------- c : Circuit Input circuit. Returns ------- Circuit Circuit with removed io """ g = c.graph.copy() for i in c.inputs(): g.nodes[i]["type"] = "buf" return Circuit(graph=g, name=c.name, blackboxes=c.blackboxes.copy())
def strip_outputs(c): """ Removes a circuit's outputs for easy instantiation. Parameters ---------- c : Circuit Input circuit. Returns ------- Circuit Circuit with removed io """ g = c.graph.copy() for o in c.outputs(): g.nodes[o]["type"] = "buf" return Circuit(graph=g, name=c.name, blackboxes=c.blackboxes.copy())
def comb_ff(): lm = Circuit(name="ff") # mux m = mux(2).strip_io() lm.extend(m.relabel({n: f"mux_{n}" for n in m.nodes()})) # inputs lm.add("si", "input", fanout="mux_in_0") lm.add("d", "input", fanout="mux_in_1") lm.add("clk", "input", fanout="mux_sel_0") lm.add("r", "input") lm.add("s", "input") # logic lm.add("r_b", "not", fanin="r") lm.add("qr", "and", fanin=["mux_out", "r_b"]) lm.add("q", "or", fanin=["qr", "s"], output=True) lm.add("so", "buf", fanin="q", output=True) return lm
def adder(w): """ Create an adder. Parameters ---------- w: the width of the adder. Returns ------- a `CircuitGraph` addder. """ c = Circuit(name="adder") carry = c.add("null", "0") for i in range(w): # sum c.add(f"a_{i}", "input") c.add(f"b_{i}", "input") c.add(f"out_{i}", "xor", fanin=[f"a_{i}", f"b_{i}", carry], output=True) # carry c.add(f"and_ab_{i}", "and", fanin=[f"a_{i}", f"b_{i}"]) c.add(f"and_ac_{i}", "and", fanin=[f"a_{i}", carry]) c.add(f"and_bc_{i}", "and", fanin=[f"b_{i}", carry]) carry = c.add(f"carry_{i}", "or", fanin=[ f"and_ab_{i}", f"and_ac_{i}", f"and_bc_{i}", ]) c.add(f"out_{w}", "buf", fanin=carry, output=True) return c
def circuit_to_verilog(c): """ Generates a str of Verilog code from a `CircuitGraph`. Parameters ---------- c: Circuit the circuit to turn into Verilog. Returns ------- str Verilog code. """ c = Circuit(graph=c.graph.copy(), name=c.name, blackboxes=c.blackboxes.copy()) # sanitize escaped nets for node in c.nodes(): if node.startswith("\\"): c.relabel({node: node + " "}) inputs = list(c.inputs()) outputs = list(c.outputs()) insts = [] wires = [] # remove outputs drivers driver_mapping = dict() for output in outputs: if len(c.fanin(output)) > 1: raise ValueError(f"Output {output} has multiple drivers.") elif len(c.fanin(output)) == 1: driver = c.fanin(output).pop() if c.type(driver) in ["input", "1", "0"]: driver = c.add(f"{output}_driver", type="buf", fanin=driver, uid=True) driver_mapping[driver] = output c.remove(c.outputs()) c.relabel(driver_mapping) # blackboxes output_map = {} for name, bb in c.blackboxes.items(): io = [] for n in bb.inputs(): driver = c.fanin(f"{name}.{n}").pop() io += [f".{n}({driver})"] for n in bb.outputs(): w = c.uid(f"{name}_{n}_load") wires.append(w) output_map[f"{name}.{n}"] = w io += [f".{n}({w})"] io_def = ", ".join(io) insts.append(f"{bb.name} {name} ({io_def})") # gates for n in c.nodes(): if c.type(n) in [ "xor", "xnor", "buf", "not", "nor", "or", "and", "nand" ]: fanin = [ output_map[f] if f in output_map else f for f in c.fanin(n) ] fanin = ", ".join(fanin) insts.append(f"{c.type(n)} g_{len(insts)} " f"({n}, {fanin})") wires.append(n) elif c.type(n) in ["0", "1"]: insts.append(f"assign {n} = 1'b{c.type(n)}") wires.append(n) elif c.type(n) in ["input", "output", "bb_input", "bb_output"]: pass else: raise ValueError(f"unknown gate type: {c.type(n)}") verilog = f"module {c.name} (" verilog += ", ".join(inputs + outputs) verilog += ");\n" verilog += "".join(f" input {inp};\n" for inp in inputs) verilog += "\n" verilog += "".join(f" output {out};\n" for out in outputs) verilog += "\n" verilog += "".join(f" wire {wire};\n" for wire in wires) verilog += "\n" verilog += "".join(f" {inst};\n" for inst in insts) verilog += "endmodule\n" return verilog
def sensitivity_transform(c, n): """ Creates a circuit to compute sensitivity. Parameters ---------- c : Circuit Sequential circuit to unroll. n : str Node to compute sensitivity at. Returns ------- Circuit Sensitivity circuit. """ # check for blackboxes if c.blackboxes: raise ValueError(f"{c.name} contains a blackbox") # check for startpoints startpoints = c.startpoints(n) if len(startpoints) < 1: raise ValueError(f"{n} has no startpoints") # get input cone fi_nodes = c.transitive_fanin(n) | set([n]) sub_c = Circuit(graph=c.graph.subgraph(fi_nodes).copy()) # create sensitivity circuit sen = Circuit() sen.add_subcircuit(sub_c, "orig") for s in startpoints: sen.add(s, "input", fanout=f"orig_{s}") # add popcount sen.add_subcircuit(popcount(len(startpoints)), "pc") # add inverted input copies for i, s0 in enumerate(startpoints): sen.add_subcircuit(sub_c, f"inv_{s0}") # connect inputs for s1 in startpoints: if s0 != s1: sen.connect(s1, f"inv_{s0}_{s1}") else: # connect inverted input sen.set_type(f"inv_{s0}_{s1}", "not") sen.connect(s0, f"inv_{s0}_{s1}") # compare to orig sen.add( f"dif_{s0}", "xor", fanin=[f"orig_{n}", f"inv_{s0}_{n}"], fanout=f"pc_in_{i}", ) sen.add(f"dif_out_{s0}", "output", fanin=f"dif_{s0}") # instantiate population count for o in range(clog2(len(startpoints) + 1)): sen.add(f"sen_out_{o}", "output", fanin=f"pc_out_{o}") return sen
def banyan(bw): """ Create a Banyan switching network Parameters ---------- bw : int Input/output width of the network. Returns ------- Circuit Network circuit. """ b = Circuit() # generate switch m = mux(2) s = Circuit(name="switch") s.add_subcircuit(m, f"m0") s.add_subcircuit(m, f"m1") s.add("in_0", "buf", fanout=["m0_in_0", "m1_in_1"]) s.add("in_1", "buf", fanout=["m0_in_1", "m1_in_0"]) s.add("out_0", "buf", fanin="m0_out") s.add("out_1", "buf", fanin="m1_out") s.add("sel", "input", fanout=["m0_sel_0", "m1_sel_0"]) # generate banyan I = int(2 * clog2(bw) - 2) J = int(bw / 2) # add switches for i in range(I * J): b.add_subcircuit(s, f"swb_{i}") # make connections swb_ins = [f"swb_{i//2}_in_{i%2}" for i in range(I * J * 2)] swb_outs = [f"swb_{i//2}_out_{i%2}" for i in range(I * J * 2)] # connect switches for i in range(clog2(J)): r = J / (2**i) for j in range(J): t = (j % r) >= (r / 2) # straight out_i = int((i * bw) + (2 * j) + t) in_i = int((i * bw + bw) + (2 * j) + t) b.connect(swb_outs[out_i], swb_ins[in_i]) # cross out_i = int((i * bw) + (2 * j) + (1 - t) + ((r - 1) * ((1 - t) * 2 - 1))) in_i = int((i * bw + bw) + (2 * j) + (1 - t)) b.connect(swb_outs[out_i], swb_ins[in_i]) if r > 2: # straight out_i = int(((I * J * 2) - ((2 + i) * bw)) + (2 * j) + t) in_i = int(((I * J * 2) - ((1 + i) * bw)) + (2 * j) + t) b.connect(swb_outs[out_i], swb_ins[in_i]) # cross out_i = int(((I * J * 2) - ((2 + i) * bw)) + (2 * j) + (1 - t) + ((r - 1) * ((1 - t) * 2 - 1))) in_i = int(((I * J * 2) - ((1 + i) * bw)) + (2 * j) + (1 - t)) b.connect(swb_outs[out_i], swb_ins[in_i]) # create banyan io net_ins = swb_ins[:bw] net_outs = swb_outs[-bw:] for i, net_in in enumerate(net_ins): b.add(f"in_{i}", "input", fanout=net_in) for i, net_out in enumerate(net_outs): b.add(f"out_{i}", "output", fanin=net_out) for i in range(I * J): b.add(f"sel_{i}", "input", fanout=f"swb_{i}_sel") return b
def fast_parse_verilog_netlist(netlist, blackboxes): """ A fast version of `parse_verilog_netlist` that can speed up parsing on very large netlists by making a handful of assumptions. It is much safer to use `parse_verilog_netlist`. This function should only be used if necessary. The input netlist must conform to the following rules: - Only one module definition is present - There are no comments - Assign statements must have a single net as the LHS, and the RHS must be a constant - The only constants that may be used are `1'b0` and `1'b1` (or h/d) - Primitive gates can only have one output - Instantiations must be named. - Only one instantation per line (e.g. `buf b1(a, b) b2(c, d);` is not allowed) - No expressions (e.g. `buf (a, b & c);` is not allowed) - No escaped identifiers The code does not overtly check that these rules are satisfied, and if they are not this function may still return a malformed Circuit object. It is up to the caller of the function to assure that these rules are followed. If an output is undriven, a driver for the output will still be added to the circuit, which is a discrepancy with `parse_verilog_netlist` (in which no output drive will be added). Note that thorough error checking that is done in `parse_verilog_netlist` is skipped in this function (e.g. checking if nets are declared as wires, checking if the portlist matches the input/output declarations, etc.). Note also that wires that are declared but not used will not be added to the circuit. Parameters ---------- netlist: str Verilog code. blackboxes: seq of BlackBox Blackboxes in module. Returns ------- Circuit Parsed circuit. """ regex = f"module\s+(.+?)\s*\(.*?\);" m = re.search(regex, netlist, re.DOTALL) name = m.group(1) module = netlist[m.end():] regex = f"endmodule" m = re.search(regex, netlist, re.DOTALL) module = module[:m.start()] # create graph g = nx.DiGraph() # parse io regex = "(input)\s(.+?);" inputs = set() for net_type, net_str in re.findall(regex, module, re.DOTALL): nets = net_str.split(",") for net in nets: inputs.add(net.strip()) g.add_nodes_from(inputs, type="input") regex = "(output)\s(.+?);" outputs = set() for net_type, net_str in re.findall(regex, module, re.DOTALL): nets = net_str.split(",") for net in nets: outputs.add(net.strip()) g.add_nodes_from(outputs, type="output") # create output drivers, ensure unique names output_drivers = dict() for o in outputs: driver = f"{o}_driver" while driver in g: driver = f"{o}_driver_{random.randint(1111, 9999)}" output_drivers[o] = driver g.add_nodes_from(output_drivers.values(), type="wire") g.add_edges_from((v, k) for k, v in output_drivers.items()) # create constants, (will be removed if unused) tie_0 = "tie0" while tie_0 in g: tie_0 = "tie0_{random.randint(1111, 9999)}" tie_1 = "tie1" while tie_1 in g: tie_1 = "tie1_{random.randint(1111, 9999)}" g.add_node(tie_0, type="0") g.add_node(tie_1, type="1") # parse insts regex = "([a-zA-Z][a-zA-Z\d_]*)\s+([a-zA-Z][a-zA-Z\d_]*)\s*\(([^;]+)\);" all_nets = defaultdict(list) all_edges = list() blackboxes_to_add = dict() for gate, inst, net_str in re.findall(regex, module, re.DOTALL): # parse generics if gate in addable_types: # parse nets nets = [n.strip() for n in net_str.split(",")] # check for outputs, replace constants nets = [ output_drivers[n] if n in output_drivers else tie_0 if n == "1'b0" else tie_1 if n == "1'b1" else n for n in nets ] all_nets[gate].append(nets[0]) all_edges += [(i, nets[0]) for i in nets[1:]] # parse non-generics else: # get blackbox definition try: bb = next(bb for bb in blackboxes if bb.name == gate) except: raise ValueError(f"blackbox {gate} not defined") # parse pins all_nets["bb_input"] += [f"{inst}.{n}" for n in bb.inputs()] all_nets["bb_output"] += [f"{inst}.{n}" for n in bb.outputs()] regex = "\.\s*(\S+)\s*\(\s*(\S+)\s*\)" connections = {} for pin, net in re.findall(regex, net_str): # check for outputs if net in output_drivers: net = output_drivers[net] elif net == "1'b1": net = tie_1 elif net == "1'b0": net = tie_0 if pin in bb.inputs(): all_edges.append((net, f"{inst}.{pin}")) elif pin in bb.outputs(): # add intermediate net for outputs all_nets["buf"].append(net) all_edges.append((f"{inst}.{pin}", net)) else: raise ValueError( f"node {pin} not defined for blackbox {gate}") blackboxes_to_add[inst] = bb regex = "assign\s+([a-zA-Z][a-zA-Z\d_]*)\s*=\s*([a-zA-Z\d][a-zA-Z\d_']*)\s*;" for n0, n1 in re.findall(regex, module): if n0 in output_drivers: n0 = output_drivers[n0] if n1 in output_drivers: n1 = output_drivers[n1] all_nets["buf"].append(n0) if n1 in ["1'b0", "1'h0", "1'd0"]: all_edges.append((tie_0, n0)) elif n1 in ["1'b1", "1'h1", "1'd1"]: all_edges.append((tie_1, n0)) else: all_edges.append((n1, n0)) for k, v in all_nets.items(): g.add_nodes_from(v, type=k) g.add_edges_from(all_edges) try: next(g.successors(tie_0)) except StopIteration: g.remove_node(tie_0) try: next(g.successors(tie_1)) except StopIteration: g.remove_node(tie_1) return Circuit(name=name, graph=g, blackboxes=blackboxes_to_add)
def verilog_to_circuit(verilog, name, seq_types=None): """ Creates a new Circuit from a verilog file. Parameters ---------- path: str verilog code. name: str the module name. seq_types: list of dicts of str:str the sequential element types. Returns ------- Circuit the parsed circuit. """ if seq_types is None: seq_types = default_seq_types c = Circuit(name=name) with tempfile.TemporaryDirectory(prefix="circuitgraph") as d: codeparser = VerilogParser(outputdir=d, debug=False) ast = codeparser.parse(verilog, debug=False) description = ast.children()[0] module_def = [d for d in description.children() if d.name == name] if not module_def: raise ValueError(f"Module {name} not found") module_def = module_def[0] outputs = set() widths = dict() for child in module_def.children(): if type(child) == ast_types.Paramlist: if child.children(): raise ValueError( f"circuitgraph cannot parse parameters (line {child.lineno})" ) # Parse portlist elif type(child) == ast_types.Portlist: for cip in [ i for i in child.children() if type(i) == ast_types.Ioport ]: for ci in cip.children(): parse_io(c, ci, outputs) # Parse declarations elif type(child) == ast_types.Decl: if ast_types.Parameter in [type(i) for i in child.children()]: raise ValueError( f"circuitgraph cannot parse parameters (line {child.lineno})" ) for ci in [ i for i in child.children() if type(i) in [ast_types.Input, ast_types.Output] ]: parse_io(c, ci, outputs) # Parse instances elif type(child) == ast_types.InstanceList: for instance in child.instances: if instance.module in [ "buf", "not", "and", "nand", "or", "nor", "xor", "xnor", ]: gate = instance.module dest = parse_argument(instance.portlist[0].argname, c) sources = [ parse_argument(i.argname, c) for i in instance.portlist[1:] ] c.add(dest, gate, fanin=sources, output=dest in outputs) elif instance.module in [i.name for i in seq_types]: if instance.portlist[0].portname is None: raise ValueError("circuitgraph can only parse " "sequential instances declared " "with port argument notation " f"(line {instance.lineno})") seq_type = [ i for i in seq_types if i.name == instance.module ][0] ports = { p.portname: parse_argument(p.argname, c) for p in instance.portlist } c.add( ports.get(seq_type.io.get("q")), seq_type.seq_type, fanin=ports.get(seq_type.io.get("d")), clk=ports.get(seq_type.io.get("clk")), r=ports.get(seq_type.io.get("r")), s=ports.get(seq_type.io.get("s")), output=ports.get(seq_type.io.get("q")) in outputs, ) else: raise ValueError( "circuitgraph cannot parse instance of " f"type {instance.module} (line " f"{instance.lineno})") # Parse assigns elif type(child) == ast_types.Assign: dest = child.left.var dest = parse_argument(dest, c) if type(child.right.var) == ast_types.IntConst: c.add( dest, f"{child.right.var.value[-1]}", output=dest in outputs, ) elif issubclass(type(child.right.var), ast_types.Operator): parse_operator(child.right.var, c, outputs, dest=dest) elif issubclass(type(child.right.var), ast_types.Concat): raise ValueError( "circuitgraph cannot parse concatenations " f"(line {child.right.var.lineno})") else: raise ValueError( "circuitgraph cannot parse statements of type " f"{type(child)} (line {child.lineno})") return c