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 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
class VerilogCircuitGraphTransformer(Transformer): """ A lark.Transformer that transforms a parsed verilog netlist into a circuitgraph.Circuit. """ 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() # Helper functions def add_node(self, n, node_type, fanin=[], fanout=[], uid=False): """So that nodes are of type `str`, not `lark.Token`""" if type(fanin) not in [list, set]: fanin = [fanin] if type(fanout) not in [list, set]: fanout = [fanout] fanin = [str(i) for i in fanin] fanout = [str(i) for i in fanout] node_type = str(node_type) for i in fanout + fanin: if i not in self.c: self.c.add(i, "buf") return self.c.add( str(n), node_type, fanin=fanin, fanout=fanout, uid=uid, ) def add_blackbox(self, blackbox, name, connections=dict()): formatted_connections = dict() for key in connections: formatted_connections[str(key)] = str(connections[key]) if str(connections[key]) not in self.c: self.c.add(str(connections[key]), "buf") self.c.add_blackbox(blackbox, str(name), formatted_connections) def warn(self, message): if self.error_on_warning: raise VerilogParsingWarning(message) else: print(f"Warning: {message}") def check_for_warnings(self): for wire in self.wires: if wire not in self.c.nodes(): self.warn(f"{wire} declared as wire but isn't connected.") for n in self.c.nodes(): if self.c.type(n) not in ["output", "bb_input" ] and not self.c.fanout(n): self.warn(f"{n} doesn't drive any nets.") elif self.c.type(n) not in [ "input", "0", "1", "bb_output", ] and not self.c.fanin(n): self.warn(f"{n} doesn't have any drivers.") # 1. Source text def start(self, description): return description def module(self, module_name_and_list_of_ports_and_module_items): self.c.name = str(module_name_and_list_of_ports_and_module_items[0]) # Check if ports list matches with inputs and outputs if not self.inputs <= self.io: i = (self.inputs - self.io).pop() raise VerilogParsingError( f"{i} declared as output but not in port list", i, self.text) if not self.outputs <= self.io: o = (self.outputs - self.io).pop() raise VerilogParsingError( f"{o} declared as output but not in port list", o, self.text) if not self.io <= (self.inputs | self.outputs): v = (self.io - (self.inputs | self.outputs)).pop() raise VerilogParsingError( f"{v} in port list but was not declared as input or output", v, self.text, ) # Relabel outputs using drivers for o in self.outputs: o = str(o) if self.c.fanin(o): o_driver = f"{o}_driver" while o_driver in self.c.nodes(): o_driver += "_0" self.c.relabel({o: o_driver}) self.add_node(o, "output") self.c.connect(o_driver, o) # Remove tie0, tie1 if not used if not self.c.fanout(self.tie0): self.c.remove(self.tie0) if not self.c.fanout(self.tie1): self.c.remove(self.tie1) # Check for warnings if self.warnings: self.check_for_warnings() return self.c def list_of_ports(self, ports): for port in ports: self.io.add(port) # 2. Declarations def input_declaration(self, list_of_variables): [list_of_variables] = list_of_variables self.inputs.update(list_of_variables) for variable in list_of_variables: self.add_node(variable, "input") def output_declaration(self, list_of_variables): [list_of_variables] = list_of_variables self.outputs.update(list_of_variables) for variable in list_of_variables: self.add_node(variable, "output") def net_declaration(self, list_of_variables): [list_of_variables] = list_of_variables self.wires.update(list_of_variables) def list_of_variables(self, identifiers): return identifiers # 3. Primitive Instances # These are merged with module isntantiations # 4. Module Instantiations def module_instantiation(self, name_of_module_and_module_instances): name_of_module = name_of_module_and_module_instances[0] module_instances = name_of_module_and_module_instances[1:] # Check if this is a primitive gate if name_of_module in addable_types: for name, ports in module_instances: if isinstance(ports, dict): raise VerilogParsingError( "Primitive gates cannot use named port connections", name, self.text, ) self.add_node(ports[0], name_of_module, fanin=[p for p in ports[1:]]) # Check if this is a GTECH gate elif name_of_module.startswith("GTECH_") and name_of_module.split("_")[ -1].rstrip(digits).lower() in addable_types + ["zero", "one"]: gate = name_of_module.split("_")[-1].rstrip(digits).lower() if gate == "zero": gate = "0" if gate == "one": gate = "1" for name, connections in module_instances: if not isinstance(connections, dict): raise VerilogParsingError( "GTECH gates must use named port connections", name, self.text, ) output = connections["Z"] inputs = set(connections.values()) - {output} self.add_node(output, gate, fanin=inputs) # Otherwise, try to parse as blackbox else: try: bb = {i.name: i for i in self.blackboxes}[name_of_module] except KeyError: raise VerilogParsingError( f"Blackbox {name_of_module} not in list of defined blackboxes.", name_of_module, self.text, ) for name, connections in module_instances: if not isinstance(connections, dict): raise VerilogParsingError( "Blackbox instantiations must use named port connections", name, self.text, ) for output in bb.outputs(): if output in connections: self.add_node(connections[output], "buf") self.add_blackbox(bb, name, connections) def module_instance(self, name_of_instance_and_list_of_module_connecetions): ( name_of_instance, list_of_module_connecetions, ) = name_of_instance_and_list_of_module_connecetions return (name_of_instance, list_of_module_connecetions) def list_of_module_connections(self, module_port_connections): if isinstance(module_port_connections[0], dict): d = dict() for m in module_port_connections: d.update(m) return d else: return module_port_connections def module_port_connection(self, expression): return expression[0] def named_port_connection(self, identifier_and_expression): [identifier, expression] = identifier_and_expression return {identifier: expression} # 5. Behavioral Statements def assignment(self, lvalue_and_expression): [lvalue, expression] = lvalue_and_expression if lvalue not in [self.tie0, self.tie1]: self.add_node(lvalue, "buf", fanin=expression) # 6. Specify Section # 7. Expressions def expression(self, s): return s[0] def constant_zero(self, value): return self.tie0 def constant_one(self, value): return self.tie1 def not_gate(self, items): io = "_".join(items) return self.add_node(f"not_{io}", "not", fanin=items[0], uid=True) def xor_gate(self, items): io = "_".join(items) return self.add_node(f"xor_{io}", "xor", fanin=[items[0], items[1]], uid=True) def xnor_gate(self, items): io = "_".join(items) return self.add_node(f"xnor_{io}", "xnor", fanin=[items[0], items[1]], uid=True) def and_gate(self, items): io = "_".join(items) return self.add_node(f"and_{io}", "and", fanin=[items[0], items[1]], uid=True) def or_gate(self, items): io = "_".join(items) return self.add_node(f"or_{io}", "or", fanin=[items[0], items[1]], uid=True) def ternary(self, items): io = "_".join(items) n = self.add_node(f"mux_n_{io}", "not", fanin=items[0], uid=True) a0 = self.add_node(f"mux_a0_{io}", "and", fanin=[n, items[2]], uid=True) a1 = self.add_node(f"mux_a1_{io}", "and", fanin=[items[0], items[1]], uid=True) return self.add_node(f"mux_o_{io}", "or", fanin=[a0, a1], uid=True)
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