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 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