Module circuitgraph.transform
Functions for transforming circuits
Expand source code
"""Functions for transforming circuits"""
import subprocess
from tempfile import NamedTemporaryFile
import os
from pathlib import Path
import networkx as nx
from circuitgraph import Circuit
from circuitgraph.utils import clog2
from circuitgraph.logic import popcount
from circuitgraph.io import verilog_to_circuit, circuit_to_verilog
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 strip_io(c):
"""
Removes circuit's outputs and converts inputs to buffers for easy
instantiation.
Parameters
----------
c : Circuit
Input circuit.
Returns
-------
Circuit
Circuit with removed io
"""
g = c.graph.copy()
for o in c.io():
g.nodes[o]["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 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_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 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 subcircuit(c, nodes):
"""
Creates a subcircuit from a set of nodes of a given circuit.
Parameters
----------
c: Circuit
The circuit to create a subcircuit from.
nodes: list of str
The nodes to include in the subcircuit.
Returns
-------
Circuit
The subcircuit.
"""
sc = Circuit()
for node in nodes:
if c.type(node) in ["bb_output", "bb_input"]:
raise NotImplementedError("Cannot create a subcircuit with blackboxes")
sc.add(node, type=c.type(node))
for edge in c.edges():
if edge[0] in nodes and edge[1] in nodes:
sc.connect(edge[0], edge[1])
return sc
def syn(
c,
engine="yosys",
suppress_output=False,
stdout_file=None,
stderr_file=None,
working_dir=".",
fast_parsing=False,
pre_syn_file=None,
post_syn_file=None,
verilog_exists=False,
effort="high",
):
"""
Synthesizes the circuit using yosys or genus.
Parameters
----------
c : Circuit
Circuit to synthesize.
engine : str
Synthesis tool to use ('genus', 'dc', or 'yosys')
suppress_output: bool
If True, synthesis stdout will not be printed.
stdout_file: file or str or None
If defined, synthesis stdout will be directed to this file instead
of being printed.
output_file: file or str or None
If defined, synthesis stderr will be written to this file instead
of being printed.
working_dir: str
The path to run synthesis from. If using genus, this will effect
where the genus run files are stored. Directory will be created
if it does not exist.
fast_parsing: bool
If True, will use fast verilog parsing (which requires
specifically formatted netlists, see the documentation for
`verilog_to_circuit`).
pre_syn_file: file or str or None
If specified, the circuit verilog will be written to this file
before synthesis. If None, a temporary file will be used.
post_syn_file: file or str or None
If specified, the synthesis output verilog will be written to this
file. If None, a temporary file will be used.
verilog_exists: bool
If True, does not write `c` to a file, instead uses the verilog
already present in `pre_syn_file`.
effort: str
The effort to use for synthesis. Either 'high', 'medium', or 'low'
Returns
-------
Circuit
Synthesized circuit.
"""
working_dir = Path(working_dir)
working_dir.mkdir(exist_ok=True)
working_dir = str(working_dir)
# Make paths absolute in case synthesis is run from different working dir
if pre_syn_file:
pre_syn_file = Path(pre_syn_file).absolute()
if post_syn_file:
post_syn_file = Path(post_syn_file).absolute()
if verilog_exists and not pre_syn_file:
raise ValueError("Must specify pre_syn_file if using verilog_exists")
with open(pre_syn_file, "r") if verilog_exists else open(
pre_syn_file, "w"
) if pre_syn_file else NamedTemporaryFile(
prefix="circuitgraph_synthesis_input", suffix=".v", mode="w"
) as tmp_in:
if not verilog_exists:
verilog = circuit_to_verilog(c)
tmp_in.write(verilog)
tmp_in.flush()
with open(post_syn_file, "w+") if post_syn_file else NamedTemporaryFile(
prefix="circuitgraph_synthesis_output", suffix=".v", mode="r"
) as tmp_out:
if engine == "genus":
try:
lib_path = os.environ["CIRCUITGRAPH_GENUS_LIBRARY_PATH"]
except KeyError:
raise ValueError(
"In order to run synthesis with Genus, "
"please set the "
"CIRCUITGRAPH_GENUS_LIBRARY_PATH "
"variable in your os environment to the "
"path to the tech library to use"
)
cmd = [
"genus",
"-no_gui",
"-execute",
"set_db / .library "
f"{lib_path};\n"
f"read_hdl -sv {tmp_in.name};\n"
"elaborate;\n"
f"set_db syn_generic_effort {effort};\n"
"syn_generic;\n"
"syn_map;\n"
"syn_opt;\n"
f'redirect {tmp_out.name} "write_hdl -generic";\n'
"exit;",
]
elif engine == "dc":
try:
lib_path = os.environ["CIRCUITGRAPH_DC_LIBRARY_PATH"]
except KeyError:
raise ValueError(
"In order to run synthesis with DC, "
"please set the "
"CIRCUITGRAPH_DC_LIBRARY_PATH "
"variable in your os environment to the "
"path to the GTECH library"
)
execute = (
f"set_app_var target_library {lib_path};\n"
f"set_app_var link_library {lib_path};\n"
)
unusable_cells = [
"GTECH_ADD*",
"GTECH_AO*",
"GTECH_AND_NOT",
"GTECH_FD1S",
"GTECH_FD2S",
"GTECH_FD3S",
"GTECH_FD4",
"GTECH_FD4S",
"GTECH_FD14",
"GTECH_FD18",
"GTECH_FD24",
"GTECH_FD28",
"GTECH_FD34",
"GTECH_FD38",
"GTECH_FD44",
"GTECH_FD48",
"GTECH_FJK1",
"GTECH_FJK1S",
"GTECH_FJK2",
"GTECH_FJK2S",
"GTECH_FJK3",
"GTECH_FJK3S",
"GTECH_INBUF",
"GTECH_INOUTBUF",
"GTECH_ISO0_EN0",
"GTECH_ISO0_EN1",
"GTECH_ISO1_EN0",
"GTECH_ISO1_EN1",
"GTECH_ISOLATCH_EN0",
"GTECH_ISOLATCH_EN1",
"GTECH_LD2",
"GTECH_LD2_1",
"GTECH_LD3",
"GTECH_LD4",
"GTECH_LD4_1",
"GTECH_LSR0",
"GTECH_MAJ23",
"GTECH_MUX*",
"GTECH_OA*",
"GTECH_OR_NOT",
"GTECH_OUTBUF",
"GTECH_TBUF",
]
for cell in unusable_cells:
execute += f"set_dont_use gtech/{cell};\n"
execute += (
f"read_file {tmp_in.name}\n"
"link;\n"
"uniquify;\n"
"check_design;\n"
"simplify_constants;\n"
f"compile -map_effort {effort};\n"
f"write -format verilog -output {tmp_out.name};\n"
"exit;"
)
cmd = ["dc_shell-t", "-no_gui", "-x", execute]
elif engine == "yosys":
cmd = [
"yosys",
"-p",
f"read_verilog {tmp_in.name}; "
"synth; "
f"write_verilog -noattr {tmp_out.name}",
]
else:
raise ValueError("synthesis engine must be yosys, dc, or genus")
if suppress_output and not stdout_file:
stdout = subprocess.DEVNULL
elif stdout_file:
stdout = open(stdout_file, "w")
else:
stdout = None
if stderr_file:
stderr = open(stderr_file, "w")
else:
stderr = None
subprocess.run(cmd, stdout=stdout, stderr=stderr, cwd=working_dir)
if stdout_file:
stdout.close()
if stderr_file:
stderr.close()
output_netlist = tmp_out.read()
return verilog_to_circuit(output_netlist, c.name, fast=fast_parsing)
def ternary(c):
"""
Encodes the circuit with ternary values
Parameters
----------
c : Circuit
Circuit to encode.
Returns
-------
Circuit
Encoded circuit.
"""
if c.blackboxes:
raise ValueError(f"{c.name} contains a blackbox")
t = copy(c)
# add dual nodes
for n in c:
if c.type(n) in ["and", "nand"]:
t.add(f"{n}_x", "and")
t.add(
f"{n}_x_in_fi",
"or",
fanout=f"{n}_x",
fanin=[f"{p}_x" for p in c.fanin(n)],
)
t.add(f"{n}_0_not_in_fi", "nor", fanout=f"{n}_x")
for p in c.fanin(n):
t.add(
f"{p}_is_0", "nor", fanout=f"{n}_0_not_in_fi", fanin=[p, f"{p}_x"]
)
elif c.type(n) in ["or", "nor"]:
t.add(f"{n}_x", "and")
t.add(
f"{n}_x_in_fi",
"or",
fanout=f"{n}_x",
fanin=[f"{p}_x" for p in c.fanin(n)],
)
t.add(f"{n}_1_not_in_fi", "nor", fanout=f"{n}_x")
for p in c.fanin(n):
t.add(f"{p}_is_1", "and", fanout=f"{n}_1_not_in_fi", fanin=p)
t.add(f"{p}_not_x", "not", fanout=f"{p}_is_1", fanin=f"{p}_x")
elif c.type(n) in ["buf", "not"]:
p = c.fanin(n).pop()
t.add(f"{n}_x", "buf", fanin=f"{p}_x")
elif c.type(n) in ["output"]:
p = c.fanin(n).pop()
t.add(f"{n}_x", "output", fanin=f"{p}_x")
elif c.type(n) in ["xor", "xnor"]:
t.add(f"{n}_x", "or", fanin=(f"{p}_x" for p in c.fanin(n)))
elif c.type(n) in ["0", "1"]:
t.add(f"{n}_x", "0")
elif c.type(n) in ["input"]:
t.add(f"{n}_x", "input")
else:
raise ValueError(f"Node {n} has unrecognized type: {c.type(n)}")
return t
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 sequential_unroll(c, n, reg_d_port, reg_q_port, remove_unused_ports=True):
"""
Unroll a sequential circuit. Provides a higher level API than `unroll`
by accepting a circuit with sequential elements kept as blackboxes.
Assumes that all blackboxes in the circuit are sequential elements.
Parameters
----------
c: Circuit
Circuit to unroll.
n: int
The number of times to unroll.
reg_d_port: str
The name of the D port in the blackboxes in `c`.
reg_q_port: str
The name of the Q port in the blackboxes in `cc`.
remove_unused_ports: bool
If True, any inputs to the combinational circuit without drivers will
be removed before returning. This can be used to remove drivers for
the register ports besides `reg_d_port` and `reg_q_port`, e.g. a clk
or rst.
Returns
-------
Circuit
The unrolled circuit.
"""
cs = strip_blackboxes(c)
blackbox = c.blackboxes[set(c.blackboxes.keys()).pop()]
if reg_d_port not in blackbox.inputs():
raise ValueError(f"Provided d port {reg_d_port} not in bb inputs")
cs.remove(
f"{bb}_{p}" for p in blackbox.inputs() - {reg_d_port} for bb in c.blackboxes
)
if reg_q_port not in blackbox.outputs():
raise ValueError(f"Provided q port {reg_q_port} not in bb outputs")
cs.remove(
f"{bb}_{p}" for p in blackbox.outputs() - {reg_q_port} for bb in c.blackboxes
)
cs.remove(i for i in cs.inputs() if not cs.fanout(i))
state_io = {f"{bb}_{reg_d_port}": f"{bb}_{reg_q_port}" for bb in c.blackboxes}
uc = unroll(cs, n, state_io)
return uc
def unroll(c, n, state_io):
"""
Unrolls a circuit.
Parameters
----------
c: Circuit
Circuit to unroll.
n: int
The number of times to unroll.
state_io: dict of str:str
For each `(k, v)` pair in the dict, `k` of circuit iteration `n - 1` will be
tied to `v` of circuit iteration `n`.
Returns
-------
Circuit
Unrolled circuit.
"""
# check for blackboxes
if c.blackboxes:
raise ValueError(f"{c.name} contains a blackbox")
if n < 1:
raise ValueError(f"n must be >= 1 ({n})")
uc = Circuit()
for itr in range(n + 1):
for i in c.io():
if f"{i}_{itr}" in c:
raise ValueError(f"Naming clash: {i}_{itr} already in circuit")
if i in state_io or i in state_io.values():
t = "buf"
elif i in c.inputs():
t = "input"
elif i in c.outputs():
t = "output"
uc.add(f"{i}_{itr}", t)
uc.add_subcircuit(c, f"unrolled_{itr}", {i: f"{i}_{itr}" for i in c.io()})
if itr == 0:
for i in state_io.values():
uc.set_type(f"{i}_{itr}", "input")
else:
for k, v in state_io.items():
uc.connect(f"{k}_{itr-1}", f"{v}_{itr}")
if itr == n:
for i in state_io:
uc.set_type(f"{i}_{itr}", "output")
return uc
def influence_transform(c, n, s):
"""
Creates a circuit to compute influence.
Parameters
----------
c : Circuit
Sequential circuit to compute influence for.
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 sensitivity_transform(c, n):
"""
Creates a circuit to compute sensitivity.
Parameters
----------
c : Circuit
Sequential circuit to ccompute sensitivity for.
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 sensitization_transform(c, n):
"""
Creates a circuit to sensitize a node to an endpoint.
Parameters
----------
c : Circuit
Input circuit.
n : str
Node to sensitize.
Returns
-------
Circuit
Output circuit.
"""
# create miter
m = miter(c)
m.name = f"{c.name}_sensitized_{n}"
# flip node in c1
m.disconnect(m.fanin(f"c1_{n}"), f"c1_{n}")
m.set_type(f"c1_{n}", "not")
m.connect(f"c0_{n}", f"c1_{n}")
return m
def limit_fanin(c, k):
"""
Reduces the maximum fanin of circuit gates to k
Parameters
----------
c : Circuit
Input circuit.
k : str
Maximum fanin. (k>1)
Returns
-------
Circuit
Output circuit.
"""
if k < 2:
raise ValueError(f"maximum fanin, k, must be > 2")
ck = copy(c)
for n in ck.nodes():
i = 0
while len(ck.fanin(n)) > k:
fi = ck.fanin(n)
f0 = fi.pop()
f1 = fi.pop()
ck.disconnect([f0, f1], n)
if ck.type(n) in ["and", "nand"]:
ck.add(f"{n}_new_{i}", "and", fanin=[f0, f1], fanout=n)
elif ck.type(n) in ["or", "nor"]:
ck.add(f"{n}_new_{i}", "or", fanin=[f0, f1], fanout=n)
elif ck.type(n) in ["xor"]:
ck.add(f"{n}_new_{i}", "xor", fanin=[f0, f1], fanout=n)
elif ck.type(n) in ["xnor"]:
ck.add(f"{n}_new_{i}", "xnor", fanin=[f0, f1], fanout=n)
else:
raise ValueError(f"Unknown gate type: {ck.type(n)}")
i += 1
return ck
Functions
def copy(c)
-
Returns copy of a circuit.
Parameters
c
:Circuit
- Input circuit.
Returns
Circuit
- Circuit copy.
Expand source code
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 influence_transform(c, n, s)
-
Creates a circuit to compute influence.
Parameters
c
:Circuit
- Sequential circuit to compute influence for.
n
:str
- Node to compute influence at.
s
:str
- Startpoint to compute influence for.
Returns
Circuit
- Influence circuit.
Expand source code
def influence_transform(c, n, s): """ Creates a circuit to compute influence. Parameters ---------- c : Circuit Sequential circuit to compute influence for. 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 limit_fanin(c, k)
-
Reduces the maximum fanin of circuit gates to k
Parameters
c
:Circuit
- Input circuit.
k
:str
- Maximum fanin. (k>1)
Returns
Circuit
- Output circuit.
Expand source code
def limit_fanin(c, k): """ Reduces the maximum fanin of circuit gates to k Parameters ---------- c : Circuit Input circuit. k : str Maximum fanin. (k>1) Returns ------- Circuit Output circuit. """ if k < 2: raise ValueError(f"maximum fanin, k, must be > 2") ck = copy(c) for n in ck.nodes(): i = 0 while len(ck.fanin(n)) > k: fi = ck.fanin(n) f0 = fi.pop() f1 = fi.pop() ck.disconnect([f0, f1], n) if ck.type(n) in ["and", "nand"]: ck.add(f"{n}_new_{i}", "and", fanin=[f0, f1], fanout=n) elif ck.type(n) in ["or", "nor"]: ck.add(f"{n}_new_{i}", "or", fanin=[f0, f1], fanout=n) elif ck.type(n) in ["xor"]: ck.add(f"{n}_new_{i}", "xor", fanin=[f0, f1], fanout=n) elif ck.type(n) in ["xnor"]: ck.add(f"{n}_new_{i}", "xnor", fanin=[f0, f1], fanout=n) else: raise ValueError(f"Unknown gate type: {ck.type(n)}") i += 1 return ck
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
ofstr
- Nodes to be tied together, must exist in both circuits.
endpoints
:set
ofstr
- Nodes to be compared, must exist in both circuits.
Returns
Circuit
- Miter circuit.
Expand source code
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 relabel(c, mapping)
-
Builds copy with relabeled nodes.
Parameters
c
:Circuit
- Input circuit.
mapping
:dict
ofstr:str
- Relabeling of nodes.
Returns
Circuit
- Circuit with removed blackboxes.
Expand source code
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 sensitivity_transform(c, n)
-
Creates a circuit to compute sensitivity.
Parameters
c
:Circuit
- Sequential circuit to ccompute sensitivity for.
n
:str
- Node to compute sensitivity at.
Returns
Circuit
- Sensitivity circuit.
Expand source code
def sensitivity_transform(c, n): """ Creates a circuit to compute sensitivity. Parameters ---------- c : Circuit Sequential circuit to ccompute sensitivity for. 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 sensitization_transform(c, n)
-
Creates a circuit to sensitize a node to an endpoint.
Parameters
c
:Circuit
- Input circuit.
n
:str
- Node to sensitize.
Returns
Circuit
- Output circuit.
Expand source code
def sensitization_transform(c, n): """ Creates a circuit to sensitize a node to an endpoint. Parameters ---------- c : Circuit Input circuit. n : str Node to sensitize. Returns ------- Circuit Output circuit. """ # create miter m = miter(c) m.name = f"{c.name}_sensitized_{n}" # flip node in c1 m.disconnect(m.fanin(f"c1_{n}"), f"c1_{n}") m.set_type(f"c1_{n}", "not") m.connect(f"c0_{n}", f"c1_{n}") return m
def sequential_unroll(c, n, reg_d_port, reg_q_port, remove_unused_ports=True)
-
Unroll a sequential circuit. Provides a higher level API than
unroll()
by accepting a circuit with sequential elements kept as blackboxes. Assumes that all blackboxes in the circuit are sequential elements.Parameters
c
:Circuit
- Circuit to unroll.
n
:int
- The number of times to unroll.
reg_d_port
:str
- The name of the D port in the blackboxes in
c
. reg_q_port
:str
- The name of the Q port in the blackboxes in
cc
. remove_unused_ports
:bool
- If True, any inputs to the combinational circuit without drivers will
be removed before returning. This can be used to remove drivers for
the register ports besides
reg_d_port
andreg_q_port
, e.g. a clk or rst.
Returns
Circuit
- The unrolled circuit.
Expand source code
def sequential_unroll(c, n, reg_d_port, reg_q_port, remove_unused_ports=True): """ Unroll a sequential circuit. Provides a higher level API than `unroll` by accepting a circuit with sequential elements kept as blackboxes. Assumes that all blackboxes in the circuit are sequential elements. Parameters ---------- c: Circuit Circuit to unroll. n: int The number of times to unroll. reg_d_port: str The name of the D port in the blackboxes in `c`. reg_q_port: str The name of the Q port in the blackboxes in `cc`. remove_unused_ports: bool If True, any inputs to the combinational circuit without drivers will be removed before returning. This can be used to remove drivers for the register ports besides `reg_d_port` and `reg_q_port`, e.g. a clk or rst. Returns ------- Circuit The unrolled circuit. """ cs = strip_blackboxes(c) blackbox = c.blackboxes[set(c.blackboxes.keys()).pop()] if reg_d_port not in blackbox.inputs(): raise ValueError(f"Provided d port {reg_d_port} not in bb inputs") cs.remove( f"{bb}_{p}" for p in blackbox.inputs() - {reg_d_port} for bb in c.blackboxes ) if reg_q_port not in blackbox.outputs(): raise ValueError(f"Provided q port {reg_q_port} not in bb outputs") cs.remove( f"{bb}_{p}" for p in blackbox.outputs() - {reg_q_port} for bb in c.blackboxes ) cs.remove(i for i in cs.inputs() if not cs.fanout(i)) state_io = {f"{bb}_{reg_d_port}": f"{bb}_{reg_q_port}" for bb in c.blackboxes} uc = unroll(cs, n, state_io) return uc
def strip_blackboxes(c, ignore_pins=None)
-
Converts blackboxes to io.
Parameters
c
:Circuit
- Input circuit.
ingnore_pins
:str
orlist
ofstr
- Pins to not create io for, just disconnect and delete.
Returns
Circuit
- Circuit with removed blackboxes.
Expand source code
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
Expand source code
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_io(c)
-
Removes circuit's outputs and converts inputs to buffers for easy instantiation.
Parameters
c
:Circuit
- Input circuit.
Returns
Circuit
- Circuit with removed io
Expand source code
def strip_io(c): """ Removes circuit's outputs and converts inputs to buffers for easy instantiation. Parameters ---------- c : Circuit Input circuit. Returns ------- Circuit Circuit with removed io """ g = c.graph.copy() for o in c.io(): g.nodes[o]["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
Expand source code
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 subcircuit(c, nodes)
-
Creates a subcircuit from a set of nodes of a given circuit.
Parameters
c
:Circuit
- The circuit to create a subcircuit from.
nodes
:list
ofstr
- The nodes to include in the subcircuit.
Returns
Circuit
- The subcircuit.
Expand source code
def subcircuit(c, nodes): """ Creates a subcircuit from a set of nodes of a given circuit. Parameters ---------- c: Circuit The circuit to create a subcircuit from. nodes: list of str The nodes to include in the subcircuit. Returns ------- Circuit The subcircuit. """ sc = Circuit() for node in nodes: if c.type(node) in ["bb_output", "bb_input"]: raise NotImplementedError("Cannot create a subcircuit with blackboxes") sc.add(node, type=c.type(node)) for edge in c.edges(): if edge[0] in nodes and edge[1] in nodes: sc.connect(edge[0], edge[1]) return sc
def syn(c, engine='yosys', suppress_output=False, stdout_file=None, stderr_file=None, working_dir='.', fast_parsing=False, pre_syn_file=None, post_syn_file=None, verilog_exists=False, effort='high')
-
Synthesizes the circuit using yosys or genus.
Parameters
c
:Circuit
- Circuit to synthesize.
engine
:str
- Synthesis tool to use ('genus', 'dc', or 'yosys')
suppress_output
:bool
- If True, synthesis stdout will not be printed.
stdout_file
:file
orstr
orNone
- If defined, synthesis stdout will be directed to this file instead of being printed.
output_file
:file
orstr
orNone
- If defined, synthesis stderr will be written to this file instead of being printed.
working_dir
:str
- The path to run synthesis from. If using genus, this will effect where the genus run files are stored. Directory will be created if it does not exist.
fast_parsing
:bool
- If True, will use fast verilog parsing (which requires
specifically formatted netlists, see the documentation for
verilog_to_circuit
). pre_syn_file
:file
orstr
orNone
- If specified, the circuit verilog will be written to this file before synthesis. If None, a temporary file will be used.
post_syn_file
:file
orstr
orNone
- If specified, the synthesis output verilog will be written to this file. If None, a temporary file will be used.
verilog_exists
:bool
- If True, does not write
c
to a file, instead uses the verilog already present inpre_syn_file
. effort
:str
- The effort to use for synthesis. Either 'high', 'medium', or 'low'
Returns
Circuit
- Synthesized circuit.
Expand source code
def syn( c, engine="yosys", suppress_output=False, stdout_file=None, stderr_file=None, working_dir=".", fast_parsing=False, pre_syn_file=None, post_syn_file=None, verilog_exists=False, effort="high", ): """ Synthesizes the circuit using yosys or genus. Parameters ---------- c : Circuit Circuit to synthesize. engine : str Synthesis tool to use ('genus', 'dc', or 'yosys') suppress_output: bool If True, synthesis stdout will not be printed. stdout_file: file or str or None If defined, synthesis stdout will be directed to this file instead of being printed. output_file: file or str or None If defined, synthesis stderr will be written to this file instead of being printed. working_dir: str The path to run synthesis from. If using genus, this will effect where the genus run files are stored. Directory will be created if it does not exist. fast_parsing: bool If True, will use fast verilog parsing (which requires specifically formatted netlists, see the documentation for `verilog_to_circuit`). pre_syn_file: file or str or None If specified, the circuit verilog will be written to this file before synthesis. If None, a temporary file will be used. post_syn_file: file or str or None If specified, the synthesis output verilog will be written to this file. If None, a temporary file will be used. verilog_exists: bool If True, does not write `c` to a file, instead uses the verilog already present in `pre_syn_file`. effort: str The effort to use for synthesis. Either 'high', 'medium', or 'low' Returns ------- Circuit Synthesized circuit. """ working_dir = Path(working_dir) working_dir.mkdir(exist_ok=True) working_dir = str(working_dir) # Make paths absolute in case synthesis is run from different working dir if pre_syn_file: pre_syn_file = Path(pre_syn_file).absolute() if post_syn_file: post_syn_file = Path(post_syn_file).absolute() if verilog_exists and not pre_syn_file: raise ValueError("Must specify pre_syn_file if using verilog_exists") with open(pre_syn_file, "r") if verilog_exists else open( pre_syn_file, "w" ) if pre_syn_file else NamedTemporaryFile( prefix="circuitgraph_synthesis_input", suffix=".v", mode="w" ) as tmp_in: if not verilog_exists: verilog = circuit_to_verilog(c) tmp_in.write(verilog) tmp_in.flush() with open(post_syn_file, "w+") if post_syn_file else NamedTemporaryFile( prefix="circuitgraph_synthesis_output", suffix=".v", mode="r" ) as tmp_out: if engine == "genus": try: lib_path = os.environ["CIRCUITGRAPH_GENUS_LIBRARY_PATH"] except KeyError: raise ValueError( "In order to run synthesis with Genus, " "please set the " "CIRCUITGRAPH_GENUS_LIBRARY_PATH " "variable in your os environment to the " "path to the tech library to use" ) cmd = [ "genus", "-no_gui", "-execute", "set_db / .library " f"{lib_path};\n" f"read_hdl -sv {tmp_in.name};\n" "elaborate;\n" f"set_db syn_generic_effort {effort};\n" "syn_generic;\n" "syn_map;\n" "syn_opt;\n" f'redirect {tmp_out.name} "write_hdl -generic";\n' "exit;", ] elif engine == "dc": try: lib_path = os.environ["CIRCUITGRAPH_DC_LIBRARY_PATH"] except KeyError: raise ValueError( "In order to run synthesis with DC, " "please set the " "CIRCUITGRAPH_DC_LIBRARY_PATH " "variable in your os environment to the " "path to the GTECH library" ) execute = ( f"set_app_var target_library {lib_path};\n" f"set_app_var link_library {lib_path};\n" ) unusable_cells = [ "GTECH_ADD*", "GTECH_AO*", "GTECH_AND_NOT", "GTECH_FD1S", "GTECH_FD2S", "GTECH_FD3S", "GTECH_FD4", "GTECH_FD4S", "GTECH_FD14", "GTECH_FD18", "GTECH_FD24", "GTECH_FD28", "GTECH_FD34", "GTECH_FD38", "GTECH_FD44", "GTECH_FD48", "GTECH_FJK1", "GTECH_FJK1S", "GTECH_FJK2", "GTECH_FJK2S", "GTECH_FJK3", "GTECH_FJK3S", "GTECH_INBUF", "GTECH_INOUTBUF", "GTECH_ISO0_EN0", "GTECH_ISO0_EN1", "GTECH_ISO1_EN0", "GTECH_ISO1_EN1", "GTECH_ISOLATCH_EN0", "GTECH_ISOLATCH_EN1", "GTECH_LD2", "GTECH_LD2_1", "GTECH_LD3", "GTECH_LD4", "GTECH_LD4_1", "GTECH_LSR0", "GTECH_MAJ23", "GTECH_MUX*", "GTECH_OA*", "GTECH_OR_NOT", "GTECH_OUTBUF", "GTECH_TBUF", ] for cell in unusable_cells: execute += f"set_dont_use gtech/{cell};\n" execute += ( f"read_file {tmp_in.name}\n" "link;\n" "uniquify;\n" "check_design;\n" "simplify_constants;\n" f"compile -map_effort {effort};\n" f"write -format verilog -output {tmp_out.name};\n" "exit;" ) cmd = ["dc_shell-t", "-no_gui", "-x", execute] elif engine == "yosys": cmd = [ "yosys", "-p", f"read_verilog {tmp_in.name}; " "synth; " f"write_verilog -noattr {tmp_out.name}", ] else: raise ValueError("synthesis engine must be yosys, dc, or genus") if suppress_output and not stdout_file: stdout = subprocess.DEVNULL elif stdout_file: stdout = open(stdout_file, "w") else: stdout = None if stderr_file: stderr = open(stderr_file, "w") else: stderr = None subprocess.run(cmd, stdout=stdout, stderr=stderr, cwd=working_dir) if stdout_file: stdout.close() if stderr_file: stderr.close() output_netlist = tmp_out.read() return verilog_to_circuit(output_netlist, c.name, fast=fast_parsing)
def ternary(c)
-
Encodes the circuit with ternary values
Parameters
c
:Circuit
- Circuit to encode.
Returns
Circuit
- Encoded circuit.
Expand source code
def ternary(c): """ Encodes the circuit with ternary values Parameters ---------- c : Circuit Circuit to encode. Returns ------- Circuit Encoded circuit. """ if c.blackboxes: raise ValueError(f"{c.name} contains a blackbox") t = copy(c) # add dual nodes for n in c: if c.type(n) in ["and", "nand"]: t.add(f"{n}_x", "and") t.add( f"{n}_x_in_fi", "or", fanout=f"{n}_x", fanin=[f"{p}_x" for p in c.fanin(n)], ) t.add(f"{n}_0_not_in_fi", "nor", fanout=f"{n}_x") for p in c.fanin(n): t.add( f"{p}_is_0", "nor", fanout=f"{n}_0_not_in_fi", fanin=[p, f"{p}_x"] ) elif c.type(n) in ["or", "nor"]: t.add(f"{n}_x", "and") t.add( f"{n}_x_in_fi", "or", fanout=f"{n}_x", fanin=[f"{p}_x" for p in c.fanin(n)], ) t.add(f"{n}_1_not_in_fi", "nor", fanout=f"{n}_x") for p in c.fanin(n): t.add(f"{p}_is_1", "and", fanout=f"{n}_1_not_in_fi", fanin=p) t.add(f"{p}_not_x", "not", fanout=f"{p}_is_1", fanin=f"{p}_x") elif c.type(n) in ["buf", "not"]: p = c.fanin(n).pop() t.add(f"{n}_x", "buf", fanin=f"{p}_x") elif c.type(n) in ["output"]: p = c.fanin(n).pop() t.add(f"{n}_x", "output", fanin=f"{p}_x") elif c.type(n) in ["xor", "xnor"]: t.add(f"{n}_x", "or", fanin=(f"{p}_x" for p in c.fanin(n))) elif c.type(n) in ["0", "1"]: t.add(f"{n}_x", "0") elif c.type(n) in ["input"]: t.add(f"{n}_x", "input") else: raise ValueError(f"Node {n} has unrecognized type: {c.type(n)}") return t
def unroll(c, n, state_io)
-
Unrolls a circuit.
Parameters
c
:Circuit
- Circuit to unroll.
n
:int
- The number of times to unroll.
state_io
:dict
ofstr:str
- For each
(k, v)
pair in the dict,k
of circuit iterationn - 1
will be tied tov
of circuit iterationn
.
Returns
Circuit
- Unrolled circuit.
Expand source code
def unroll(c, n, state_io): """ Unrolls a circuit. Parameters ---------- c: Circuit Circuit to unroll. n: int The number of times to unroll. state_io: dict of str:str For each `(k, v)` pair in the dict, `k` of circuit iteration `n - 1` will be tied to `v` of circuit iteration `n`. Returns ------- Circuit Unrolled circuit. """ # check for blackboxes if c.blackboxes: raise ValueError(f"{c.name} contains a blackbox") if n < 1: raise ValueError(f"n must be >= 1 ({n})") uc = Circuit() for itr in range(n + 1): for i in c.io(): if f"{i}_{itr}" in c: raise ValueError(f"Naming clash: {i}_{itr} already in circuit") if i in state_io or i in state_io.values(): t = "buf" elif i in c.inputs(): t = "input" elif i in c.outputs(): t = "output" uc.add(f"{i}_{itr}", t) uc.add_subcircuit(c, f"unrolled_{itr}", {i: f"{i}_{itr}" for i in c.io()}) if itr == 0: for i in state_io.values(): uc.set_type(f"{i}_{itr}", "input") else: for k, v in state_io.items(): uc.connect(f"{k}_{itr-1}", f"{v}_{itr}") if itr == n: for i in state_io: uc.set_type(f"{i}_{itr}", "output") return uc