Module circuitgraph.utils
Various circuit related utilities.
Examples
Lint a circuit to check for unloaded nets
>>> import circuitgraph as cg
>>> c = cg.from_lib("c17")
>>> c.set_output("N22", False)
>>> cg.lint(c)
Expand source code
"""
Various circuit related utilities.
Examples
--------
Lint a circuit to check for unloaded nets
>>> import circuitgraph as cg
>>> c = cg.from_lib("c17")
>>> c.set_output("N22", False)
>>> cg.lint(c)
"""
import shutil
import subprocess
from pathlib import Path
from tempfile import NamedTemporaryFile
from circuitgraph.circuit import supported_types
from circuitgraph.io import circuit_to_verilog
def visualize(c, output_file, suppress_output=True):
"""
Visualize a circuit using Yosys.
Parameters
----------
c: Circuit
Circuit to visualize.
output_file: str
Where to write the image to.
suppress_output: bool
If True, yosys stdout will not be printed.
"""
if shutil.which("yosys") is None:
raise OSError("Install 'yosys' to use 'cg.visualize'")
verilog = circuit_to_verilog(c)
output_file = Path(output_file)
fmt = output_file.suffix[1:]
prefix = output_file.with_suffix("")
if suppress_output:
stdout = subprocess.DEVNULL
else:
stdout = None
with NamedTemporaryFile(
prefix="circuitgraph_synthesis_input", suffix=".v"
) as tmp_in:
tmp_in.write(bytes(verilog, "ascii"))
tmp_in.flush()
# Write dummy modules for blackboxes to show port directions
for bb in set(c.blackboxes.values()):
bb_verilog = (
f"\n\nmodule {bb.name} ({','.join(bb.inputs() | bb.outputs())});\n"
)
for i in bb.inputs():
bb_verilog += f" input {i};\n"
for o in bb.outputs():
bb_verilog += f" output {o};\n"
bb_verilog += "endmodule\n"
tmp_in.write(bytes(bb_verilog, "ascii"))
tmp_in.flush()
cmd = [
"yosys",
"-p",
f"read_verilog {tmp_in.name}; "
f"show -stretch -format {fmt} -prefix {prefix} {c.name}",
]
subprocess.run(cmd, stdout=stdout, check=True)
# Remove intermediate dot files if necessary
if fmt != "dot":
prefix.with_suffix(".dot").unlink()
def clog2(num):
r"""
Return the ceiling log base two of an integer :math:`\ge 1`.
Gives minimum dimension of a Boolean space with at least N points.
Examples
--------
Here are the values of ``clog2(N)`` for :math:`1 \le N < 18`:
>>> [clog2(n) for n in range(1, 18)]
[0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5]
This function is undefined for non-positive integers:
>>> clog2(0)
Traceback (most recent call last):
...
ValueError: expected num >= 1
"""
if num < 1:
raise ValueError("expected num >= 1")
accum, shifter = 0, 1
while num > shifter:
shifter <<= 1
accum += 1
return accum
def int_to_bin(i, w, lend=False):
"""
Convert integer to binary tuple.
Parameters
----------
i : int
Integer to convert.
w : int
Width of conversion
lend : bool
Endianness of returned tuple, helpful for iterating.
Returns
-------
tuple of bool
Binary tuple.
"""
if lend:
return tuple(reversed(tuple(v == "1" for v in bin(i)[2:].zfill(w))))
return tuple(v == "1" for v in bin(i)[2:].zfill(w))
def bin_to_int(b, lend=False):
"""
Convert binary number to integer.
Parameters
----------
b : tuple of bool
Binary tuple.
lend : bool
Endianness of tuple.
Returns
-------
int
Value as integer.
"""
if not lend:
s = "".join("1" if v else "0" for v in b)
else:
s = "".join("1" if v else "0" for v in reversed(b))
return int(s, 2)
def lint(c, fail_fast=True, unloaded=False, undriven=True, single_input_gates=False):
"""
Raise ValueError if circuit has invalid connections or types.
Parameters
----------
c: Circuit
The Circuit to lint.
fail_fast: bool
Exit after the first error.
unloaded: bool
Fail on unloaded node.
undriven: bool
Fail on undriven node.
single_input_gates: bool
Fail on multi-input gates with only a single input.
"""
errors = []
def handle(s):
if fail_fast:
raise ValueError(s)
errors.append(s)
zero_input_types = ["input", "0", "1", "bb_ouptut"]
single_input_types = ["buf", "not", "bb_input"]
multi_input_types = ["and", "nand", "or", "nor", "xor", "xnor"]
for g in c.nodes():
# check types
if "type" not in c.graph.nodes[g]:
handle(f"no type for node '{g}'")
t = c.graph.nodes[g]["type"]
if t not in supported_types:
handle(f"node '{g}' has unsupported type '{t}'")
if "." in g and g.split(".")[0] not in c.blackboxes:
handle(f"node '{g}' has blackbox syntax with no instance")
# input/constant drivers
if c.type(g) in zero_input_types and len(c.fanin(g)) > 0:
handle(f"'{c.type(g)}' node '{g}' has fanin")
# black-box output fanout
if c.type(g) == "bb_output":
if len(c.fanout(g)) > 1:
handle(f"'{c.type(g)}' node '{g}' has fanout greater than 1")
if c.fanout(g) and c.type(c.fanout(g).pop()) != "buf":
handle(f"'{c.type(g)}' node '{g}' has non-buf fanout")
# multiple drivers
if c.type(g) in single_input_types and len(c.fanin(g)) > 1:
handle(f"'{c.type(g)}' node '{g}' has fanin count > 1")
# no drivers
if (
undriven
and c.type(g) in single_input_types + multi_input_types
and len(c.fanin(g)) < 1
):
handle(f"'{c.type(g)}' node '{g}' has no fanin")
# single drivers
if (
single_input_gates
and c.type(g) in multi_input_types
and len(c.fanin(g)) < 2
):
handle(f"'{c.type(g)}' node '{g}' has fanin less than 2")
# unloaded
if unloaded and not c.is_output(g) and not c.fanout(g):
handle(f"'{c.type(g)}' node '{g}' has no fanout")
# blackboxes
for name, bb in c.blackboxes.items():
for g in bb.inputs():
if f"{name}.{g}" not in c.graph.nodes:
handle(f"missing blackbox pin '{name}.{g}'")
else:
t = c.graph.nodes[f"{name}.{g}"]["type"]
if t != "bb_input":
handle(f"blackbox pin '{name}.{g}' has incorrect type '{t}'")
for g in bb.outputs():
if f"{name}.{g}" not in c.graph.nodes:
handle(f"missing blackbox pin '{name}.{g}'")
else:
t = c.graph.nodes[f"{name}.{g}"]["type"]
if t != "bb_output":
handle(f"blackbox pin '{name}.{g}' has incorrect type '{t}'")
if errors:
msg = "f{len(errors}} total errors.\n"
if len(errors) > 10:
msg += "\n".join(errors[:10])
msg += f"\nplus {len(errors) - 10} other errors..."
else:
msg += "\n".join(errors)
raise ValueError(msg)
Functions
def bin_to_int(b, lend=False)
-
Convert binary number to integer.
Parameters
b
:tuple
ofbool
- Binary tuple.
lend
:bool
- Endianness of tuple.
Returns
int
- Value as integer.
Expand source code
def bin_to_int(b, lend=False): """ Convert binary number to integer. Parameters ---------- b : tuple of bool Binary tuple. lend : bool Endianness of tuple. Returns ------- int Value as integer. """ if not lend: s = "".join("1" if v else "0" for v in b) else: s = "".join("1" if v else "0" for v in reversed(b)) return int(s, 2)
def clog2(num)
-
Return the ceiling log base two of an integer :math:
\ge 1
.Gives minimum dimension of a Boolean space with at least N points.
Examples
Here are the values of
clog2()(N)
for :math:1 \le N < 18
:>>> [clog2(n) for n in range(1, 18)] [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5]
This function is undefined for non-positive integers:
>>> clog2(0) Traceback (most recent call last): ... ValueError: expected num >= 1
Expand source code
def clog2(num): r""" Return the ceiling log base two of an integer :math:`\ge 1`. Gives minimum dimension of a Boolean space with at least N points. Examples -------- Here are the values of ``clog2(N)`` for :math:`1 \le N < 18`: >>> [clog2(n) for n in range(1, 18)] [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5] This function is undefined for non-positive integers: >>> clog2(0) Traceback (most recent call last): ... ValueError: expected num >= 1 """ if num < 1: raise ValueError("expected num >= 1") accum, shifter = 0, 1 while num > shifter: shifter <<= 1 accum += 1 return accum
def int_to_bin(i, w, lend=False)
-
Convert integer to binary tuple.
Parameters
i
:int
- Integer to convert.
w
:int
- Width of conversion
lend
:bool
- Endianness of returned tuple, helpful for iterating.
Returns
tuple
ofbool
- Binary tuple.
Expand source code
def int_to_bin(i, w, lend=False): """ Convert integer to binary tuple. Parameters ---------- i : int Integer to convert. w : int Width of conversion lend : bool Endianness of returned tuple, helpful for iterating. Returns ------- tuple of bool Binary tuple. """ if lend: return tuple(reversed(tuple(v == "1" for v in bin(i)[2:].zfill(w)))) return tuple(v == "1" for v in bin(i)[2:].zfill(w))
def lint(c, fail_fast=True, unloaded=False, undriven=True, single_input_gates=False)
-
Raise ValueError if circuit has invalid connections or types.
Parameters
c
:Circuit
- The Circuit to lint.
fail_fast
:bool
- Exit after the first error.
unloaded
:bool
- Fail on unloaded node.
undriven
:bool
- Fail on undriven node.
single_input_gates
:bool
- Fail on multi-input gates with only a single input.
Expand source code
def lint(c, fail_fast=True, unloaded=False, undriven=True, single_input_gates=False): """ Raise ValueError if circuit has invalid connections or types. Parameters ---------- c: Circuit The Circuit to lint. fail_fast: bool Exit after the first error. unloaded: bool Fail on unloaded node. undriven: bool Fail on undriven node. single_input_gates: bool Fail on multi-input gates with only a single input. """ errors = [] def handle(s): if fail_fast: raise ValueError(s) errors.append(s) zero_input_types = ["input", "0", "1", "bb_ouptut"] single_input_types = ["buf", "not", "bb_input"] multi_input_types = ["and", "nand", "or", "nor", "xor", "xnor"] for g in c.nodes(): # check types if "type" not in c.graph.nodes[g]: handle(f"no type for node '{g}'") t = c.graph.nodes[g]["type"] if t not in supported_types: handle(f"node '{g}' has unsupported type '{t}'") if "." in g and g.split(".")[0] not in c.blackboxes: handle(f"node '{g}' has blackbox syntax with no instance") # input/constant drivers if c.type(g) in zero_input_types and len(c.fanin(g)) > 0: handle(f"'{c.type(g)}' node '{g}' has fanin") # black-box output fanout if c.type(g) == "bb_output": if len(c.fanout(g)) > 1: handle(f"'{c.type(g)}' node '{g}' has fanout greater than 1") if c.fanout(g) and c.type(c.fanout(g).pop()) != "buf": handle(f"'{c.type(g)}' node '{g}' has non-buf fanout") # multiple drivers if c.type(g) in single_input_types and len(c.fanin(g)) > 1: handle(f"'{c.type(g)}' node '{g}' has fanin count > 1") # no drivers if ( undriven and c.type(g) in single_input_types + multi_input_types and len(c.fanin(g)) < 1 ): handle(f"'{c.type(g)}' node '{g}' has no fanin") # single drivers if ( single_input_gates and c.type(g) in multi_input_types and len(c.fanin(g)) < 2 ): handle(f"'{c.type(g)}' node '{g}' has fanin less than 2") # unloaded if unloaded and not c.is_output(g) and not c.fanout(g): handle(f"'{c.type(g)}' node '{g}' has no fanout") # blackboxes for name, bb in c.blackboxes.items(): for g in bb.inputs(): if f"{name}.{g}" not in c.graph.nodes: handle(f"missing blackbox pin '{name}.{g}'") else: t = c.graph.nodes[f"{name}.{g}"]["type"] if t != "bb_input": handle(f"blackbox pin '{name}.{g}' has incorrect type '{t}'") for g in bb.outputs(): if f"{name}.{g}" not in c.graph.nodes: handle(f"missing blackbox pin '{name}.{g}'") else: t = c.graph.nodes[f"{name}.{g}"]["type"] if t != "bb_output": handle(f"blackbox pin '{name}.{g}' has incorrect type '{t}'") if errors: msg = "f{len(errors}} total errors.\n" if len(errors) > 10: msg += "\n".join(errors[:10]) msg += f"\nplus {len(errors) - 10} other errors..." else: msg += "\n".join(errors) raise ValueError(msg)
def visualize(c, output_file, suppress_output=True)
-
Visualize a circuit using Yosys.
Parameters
c
:Circuit
- Circuit to visualize.
output_file
:str
- Where to write the image to.
suppress_output
:bool
- If True, yosys stdout will not be printed.
Expand source code
def visualize(c, output_file, suppress_output=True): """ Visualize a circuit using Yosys. Parameters ---------- c: Circuit Circuit to visualize. output_file: str Where to write the image to. suppress_output: bool If True, yosys stdout will not be printed. """ if shutil.which("yosys") is None: raise OSError("Install 'yosys' to use 'cg.visualize'") verilog = circuit_to_verilog(c) output_file = Path(output_file) fmt = output_file.suffix[1:] prefix = output_file.with_suffix("") if suppress_output: stdout = subprocess.DEVNULL else: stdout = None with NamedTemporaryFile( prefix="circuitgraph_synthesis_input", suffix=".v" ) as tmp_in: tmp_in.write(bytes(verilog, "ascii")) tmp_in.flush() # Write dummy modules for blackboxes to show port directions for bb in set(c.blackboxes.values()): bb_verilog = ( f"\n\nmodule {bb.name} ({','.join(bb.inputs() | bb.outputs())});\n" ) for i in bb.inputs(): bb_verilog += f" input {i};\n" for o in bb.outputs(): bb_verilog += f" output {o};\n" bb_verilog += "endmodule\n" tmp_in.write(bytes(bb_verilog, "ascii")) tmp_in.flush() cmd = [ "yosys", "-p", f"read_verilog {tmp_in.name}; " f"show -stretch -format {fmt} -prefix {prefix} {c.name}", ] subprocess.run(cmd, stdout=stdout, check=True) # Remove intermediate dot files if necessary if fmt != "dot": prefix.with_suffix(".dot").unlink()