Link to the the Google Colaboratory notebook
pip install pycirc
# To install from this notebook, uncomment the next line and run this cell.
# %pip install pycirc
# This should also work:
# !pip install --upgrade pycirc
# After installation, you have to restart this notebook.
# Make sure to comment the %pip or !pip lines above to avoid reinstall each time you run this notebook.
# To uninstall the package use:
# %pip uninstall pycirc
# or
# !pip uninstall pycirc
from pycirc import *
Loading builtin box cells..
We start with a very simple logic circuit which we call "FOO"
This circuit can be represented by the following PyCirc Diagram
A PyCirc diagram is a simplified form of circuit diagram in which gates are represented by text blocks rather than special shape symbols.
Define("foo")
GATE (name="x1", type="inp")
GATE (name="x2", type="inp")
GATE (name="x3", type="inp")
GATE (name="y1", type="out")
GATE (name="y2", type="out")
GATE (name="g1", type="and2")
GATE (name="g2", type="not")
WIRE ("x1", "g1/x1")
WIRE ("x2", "g1/x2")
WIRE ("x3", "g2/x")
WIRE ("g1/y", "y1")
WIRE ("g2/y", "y2")
EndDef()
Cell = foo: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f011d8fae90>
Notic that this is a pure Python code!
A circuit definition starts with the Define()
function
call which accepts the circuit name as its first argument.
EndDef()
function call.GATE
and WIRE
commands.GATE
function accepts the name and
the logic type of the gate.WIRE
function connects a source pin
to a target pin.g/p
consists of a gate name
g
and a pin name p
.p
is either an input or an output pin
of the gate."g1/x2"
designates
the input pin "x2"
of the gate "g1"
.GATE("x1", type="inp")
load("foo")
set_path(["c:/eda/pycirc/lib", "d:/cmfl/code/logcirc/lib", "https://samyzaf.com/pycirc/lib"])
We will use this library path in this notebook, but you can download all circuit files from the following url, place them on your local computer, and change the path accordingly.
In most cases it is better to use the need command
need("foo")
which loads a circuit only if it was not already loaded.
# You need to edit this list.
# Replace the first two library paths to ones in your local system.
# Or just leave the last one to load cells from pycirc web site.
set_path(["c:/eda/pycirc/lib", "d:/cmfl/code/logcirc/lib1", "https://samyzaf.com/pycirc/lib"])
set_path([ dir1, dir2, url1, url2, ...])
# CELL: FRED
# This cell is using a gate of type FOO which we defined earlier
# It will be automatically loaded.
Define("fred")
GATE("x1", type="inp") # No need to use: name="x1"
GATE("x2", type="inp")
GATE("y1", type="out")
GATE("y2", type="out")
GATE("g1", type="foo") # Here we define a gate g1 of type FOO ! <<<<<<<
GATE("g0", type="zero")
WIRE("x1", "g1/x1")
WIRE("x2", "g1/x2")
WIRE("g0", "g1/x3")
WIRE("g1/y1", "y1")
WIRE("g1/y2", "y2")
EndDef()
Cell = fred: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f0121ff1e50>
GATE("g1", type="foo")
in which the
gate of type FOO is declared! It automatically triggers the
loading of FOO (if it wasn't already loaded).It is easy to draw a PyCirc Diagram from the above definition
Note that we used the ZERO cell which has no inputs, and only one constant 0 output.
The XOR3 and NOT cells are built in types of PyCirc and are loaded automatically when PyCirc starts.
# CELL: HAM
# This cell is using the FOO cell which we defined earlier
# "xor3" is a basic cell in PyCirc - it is built in and loaded at start time.
Define("ham")
GATE("x<1:4>", type="inp") # Input gates: x1, x2, x3, x4
GATE("y<1:3>", type="out") # Output gates: y1, y2, y3
GATE("g1; g2", type="foo") # Here we define two gates g1 and g2 of type FOO !
GATE("g4", type="not")
GATE("g3; g5", type="xor3") # Here we define two gates g3 and g5 of type XOR3 !
WIRE("x1", "g1/x1")
WIRE("x2", "g1/x3")
WIRE("x3", "g1/x2; g2/x1") # Two wires defined in one line: "x3" -> "g1/x2" and "x3" -> "g2/x1"
WIRE("x4", "g2/x2; g2/x3")
WIRE("g1/y1", "g3/x2")
WIRE("g1/y2", "g3/x1; g4/x; g5/x1") # Three wires defined in one line!
WIRE("g2/y1", "g5/x2")
WIRE("g2/y2", "g3/x3; g5/x3")
WIRE("g3/y", "y1")
WIRE("g4/y", "y2")
WIRE("g5/y", "y3")
EndDef()
Cell = ham: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f011d91b6d0>
"x<1:4>"
stands for x1, x2, x3, x4
GATE("x1", type="inp")
GATE("x2", type="inp")
GATE("x3", type="inp")
GATE("x4", type="inp")
GATE("x<1:4>", type="inp")
WIRE("g1/y2", "g3/x1")
WIRE("g1/y2", "g4/x")
WIRE("g1/y2", "g5/x1")
WIRE("g1/y2", "g3/x1; g4/x; g5/x1")
WIRE("g1/y2", ["g%s/x%s" % (i,) for i in range(20)])
# Get a reference to HAM
ham = PyCirc["ham"]
ham
circuit object. which we can use to manipulate the cell.PyCirc
is the name of the Python class which creates logic circuit objects.PyCirc
keeps a map between the name and the reference
of every logic circuit created by it.ref = PyCirc[name]
ref
as any other Python
object handle for querying and manipulating the cell object.# Print the gates in the circuit HAM
for g in ham.gates:
print(g)
gate id=13: name=x1, type=inp, value=(None), depth=0 gate id=14: name=x2, type=inp, value=(None), depth=0 gate id=15: name=x3, type=inp, value=(None), depth=0 gate id=16: name=x4, type=inp, value=(None), depth=0 gate id=17: name=y1, type=out, value=(None), depth=3 gate id=18: name=y2, type=out, value=(None), depth=3 gate id=19: name=y3, type=out, value=(None), depth=3 gate id=20: name=g1, type=foo, value=(y1=None, y2=None), depth=1 gate id=21: name=g2, type=foo, value=(y1=None, y2=None), depth=1 gate id=22: name=g4, type=not, value=(y=None), depth=2 gate id=23: name=g3, type=xor3, value=(y=None), depth=2 gate id=24: name=g5, type=xor3, value=(y=None), depth=2
# Count the number of gates in the circuit HAM
print(len(ham.gates))
12
# Get names of input gates
for x in ham.input:
print(x.name)
x1 x2 x3 x4
# Get names of output gates
for x in ham.output:
print(x.name)
y1 y2 y3
# Count the number of wires in the circuit HAM
print(len(ham.wires))
16
# List all XOR3 gates
for g in ham.gates:
if g.type == "xor3":
print(g)
gate id=23: name=g3, type=xor3, value=(y=None), depth=2 gate id=24: name=g5, type=xor3, value=(y=None), depth=2
# Get a reference to gate "g1"
g1 = ham["g1"]
# Print all outcoming gates from g1
for g in ham.out_gates(g1):
print(g)
gate id=22: name=g4, type=not, value=(y=None), depth=2 gate id=23: name=g3, type=xor3, value=(y=None), depth=2 gate id=24: name=g5, type=xor3, value=(y=None), depth=2
# Print all outgoing wires from g1
for w in ham.out_wires(g1):
print(w)
wire id=16: source=gate id=20: name=g1, type=foo, value=(y1=None, y2=None), depth=1 target=gate id=23: name=g3, type=xor3, value=(y=None), depth=2 source=y1 target=x2 wire id=17: source=gate id=20: name=g1, type=foo, value=(y1=None, y2=None), depth=1 target=gate id=23: name=g3, type=xor3, value=(y=None), depth=2 source=y2 target=x1 wire id=18: source=gate id=20: name=g1, type=foo, value=(y1=None, y2=None), depth=1 target=gate id=22: name=g4, type=not, value=(y=None), depth=2 source=y2 target=x wire id=19: source=gate id=20: name=g1, type=foo, value=(y1=None, y2=None), depth=1 target=gate id=24: name=g5, type=xor3, value=(y=None), depth=2 source=y2 target=x1
for w in ham.out_wires(g1):
print("%s => %s" % (w.source, w.target))
g1/y1 => g3/x2 g1/y2 => g3/x1 g1/y2 => g4/x g1/y2 => g5/x1
# Get a reference to gate "g3"
g3 = ham["g3"]
# Print all incoming wires to gate g3
for w in ham.in_wires(g3):
print("%s ==> %s" % (w.source, w.target))
g1/y1 ==> g3/x2 g1/y2 ==> g3/x1 g2/y2 ==> g3/x3
ham["g3"]
expression in the first line.ham
has been overloaded as a dictionary which maps gate and wire names to their reference.The following diagram displays examples of typical circuit representations in the PyCirc package.
Here is a simple PyCirc Design Diagram and code for a 4x1 Multiplexer circuit (aka MUX2)
# File for MUX2
# input: x3, x2, x1, x0, s2, s1
# output: y
GATE("x0", type="inp")
GATE("x1", type="inp")
GATE("x2", type="inp")
GATE("x3", type="inp")
GATE("s1", type="inp")
GATE("s2", type="inp")
GATE("y", type="out")
GATE("g1", type="not")
GATE("g2", type="not")
GATE("g3", type="and3")
GATE("g4", type="and3")
GATE("g5", type="and3")
GATE("g6", type="and3")
GATE("g7", type="or4")
WIRE("s1", "g1/x")
WIRE("s1", "g5/x1")
WIRE("s1", "g6/x1")
WIRE("s2", "g2/x")
WIRE("s2", "g4/x3")
WIRE("s2", "g6/x3")
WIRE("x0", "g3/x2")
WIRE("x1", "g4/x2")
WIRE("x2", "g5/x2")
WIRE("x3", "g6/x2")
WIRE("g1/y", "g3/x1")
WIRE("g1/y", "g4/x1")
WIRE("g2/y", "g3/x3")
WIRE("g2/y", "g5/x3")
WIRE("g3/y", "g7/x1")
WIRE("g4/y", "g7/x2")
WIRE("g5/y", "g7/x3")
WIRE("g6/y", "g7/x4")
WIRE("g7/y", "y")
Cell = mux2: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f011d8eff10>
# File: mux2.py
# MUX2 (4x1 Multiplexer)
# input: x3, x2, x1, x0, s2, s1
# output: y
GATE("x<0:3>", type="inp") # 4 bits
GATE("s1;s2", type="inp") # 2 selectors
GATE("y", type="out")
GATE("g1; g2", type="not")
GATE("g<3:6>", type="and3") # 4 gates of basic type "and3"
GATE("g7", type="or4")
WIRE("s1", "g1/x; g5/x1; g6/x1")
WIRE("s2", "g2/x; g4/x3; g6/x3") # 3 wires!
WIRE("x<0:3>", "g<3:6>/x2")
WIRE("g1/y", "g3/x1; g4/x1")
WIRE("g2/y", "g3/x3; g5/x3")
WIRE("g<3:6>/y", "g7/x<1:4>") # 4 wires
WIRE("g7/y", "y")
Cell = mux2: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f0124aa3e90>
# get a grip on our MUX2 object
mux2 = PyCirc["mux2"]
for g in mux2.gates:
print(g)
gate id=39: name=x0, type=inp, value=(None), depth=0 gate id=40: name=x1, type=inp, value=(None), depth=0 gate id=41: name=x2, type=inp, value=(None), depth=0 gate id=42: name=x3, type=inp, value=(None), depth=0 gate id=43: name=s1, type=inp, value=(None), depth=0 gate id=44: name=s2, type=inp, value=(None), depth=0 gate id=45: name=y, type=out, value=(None), depth=4 gate id=46: name=g1, type=not, value=(y=None), depth=1 gate id=47: name=g2, type=not, value=(y=None), depth=1 gate id=48: name=g3, type=and3, value=(y=None), depth=2 gate id=49: name=g4, type=and3, value=(y=None), depth=2 gate id=50: name=g5, type=and3, value=(y=None), depth=2 gate id=51: name=g6, type=and3, value=(y=None), depth=1 gate id=52: name=g7, type=or4, value=(y=None), depth=3
print("Gates list")
for g in mux2.gates:
print("name = %s, type = %s" % (g.name, g.type))
Gates list name = x0, type = inp name = x1, type = inp name = x2, type = inp name = x3, type = inp name = s1, type = inp name = s2, type = inp name = y, type = out name = g1, type = not name = g2, type = not name = g3, type = and3 name = g4, type = and3 name = g5, type = and3 name = g6, type = and3 name = g7, type = or4
{0, 1, None}
.None
indicates uninitialized state.a = Assign(["x1", "x2", "x3", "x4"], [0,0,1,1])
Here are some code examples for manipulating an Assign object
print(a)
x1=0, x2=0, x3=1, x4=1
for x in a.names:
print(x)
x1 x2 x3 x4
a.bits()
'0011'
a.bits("x<1:3>")
'001'
a.bits(["x2", "x4"])
'01'
a
is callable (i.e., can be called as a function)a()
is the list of its bit values.a()
[0, 0, 1, 1]
a(["x2","x3"])
[0, 1]
a("x<2:4>")
[0, 1, 1]
a = Assign.fromKeys(x1=0, x2=0, x3=1, x4=1)
print(a)
x1=0, x2=0, x3=1, x4=1
None
for all bits.a = Assign("x<1:4>")
print(a)
x1=None, x2=None, x3=None, x4=None
None
value indicates that the input is uninitialized.a = Assign("x<1:4>", 0)
print(a)
x1=0, x2=0, x3=0, x4=0
gates = ['x1', 'x2', 's1', 's2']
for a in Assign.iter(gates):
print(a)
x1=0, x2=0, s1=0, s2=0 x1=0, x2=0, s1=0, s2=1 x1=0, x2=0, s1=1, s2=0 x1=0, x2=0, s1=1, s2=1 x1=0, x2=1, s1=0, s2=0 x1=0, x2=1, s1=0, s2=1 x1=0, x2=1, s1=1, s2=0 x1=0, x2=1, s1=1, s2=1 x1=1, x2=0, s1=0, s2=0 x1=1, x2=0, s1=0, s2=1 x1=1, x2=0, s1=1, s2=0 x1=1, x2=0, s1=1, s2=1 x1=1, x2=1, s1=0, s2=0 x1=1, x2=1, s1=0, s2=1 x1=1, x2=1, s1=1, s2=0 x1=1, x2=1, s1=1, s2=1
gates = ['x1', 'x2', 's1', 's2']
for a in Assign.iter(gates):
print(a.bits())
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
gates = ['x1', 'x2', 's1', 's2']
for a in Assign.iter(gates):
bitlist = a()
print(bitlist)
[0, 0, 0, 0] [0, 0, 0, 1] [0, 0, 1, 0] [0, 0, 1, 1] [0, 1, 0, 0] [0, 1, 0, 1] [0, 1, 1, 0] [0, 1, 1, 1] [1, 0, 0, 0] [1, 0, 0, 1] [1, 0, 1, 0] [1, 0, 1, 1] [1, 1, 0, 0] [1, 1, 0, 1] [1, 1, 1, 0] [1, 1, 1, 1]
None
.for g in mux2.gates:
print(g)
gate id=39: name=x0, type=inp, value=(None), depth=0 gate id=40: name=x1, type=inp, value=(None), depth=0 gate id=41: name=x2, type=inp, value=(None), depth=0 gate id=42: name=x3, type=inp, value=(None), depth=0 gate id=43: name=s1, type=inp, value=(None), depth=0 gate id=44: name=s2, type=inp, value=(None), depth=0 gate id=45: name=y, type=out, value=(None), depth=4 gate id=46: name=g1, type=not, value=(y=None), depth=1 gate id=47: name=g2, type=not, value=(y=None), depth=1 gate id=48: name=g3, type=and3, value=(y=None), depth=2 gate id=49: name=g4, type=and3, value=(y=None), depth=2 gate id=50: name=g5, type=and3, value=(y=None), depth=2 gate id=51: name=g6, type=and3, value=(y=None), depth=1 gate id=52: name=g7, type=or4, value=(y=None), depth=3
set
method is useda = Assign.fromKeys(x3=1, x2=0, x1=1, x0=0, s2=0, s1=1)
mux2.set(a) # Initilizing the cel MUX2 with assignment a
for g in mux2.gates:
print(g)
gate id=39: name=x0, type=inp, value=(0), depth=0 gate id=40: name=x1, type=inp, value=(1), depth=0 gate id=41: name=x2, type=inp, value=(0), depth=0 gate id=42: name=x3, type=inp, value=(1), depth=0 gate id=43: name=s1, type=inp, value=(1), depth=0 gate id=44: name=s2, type=inp, value=(0), depth=0 gate id=45: name=y, type=out, value=(None), depth=4 gate id=46: name=g1, type=not, value=(y=None), depth=1 gate id=47: name=g2, type=not, value=(y=None), depth=1 gate id=48: name=g3, type=and3, value=(y=None), depth=2 gate id=49: name=g4, type=and3, value=(y=None), depth=2 gate id=50: name=g5, type=and3, value=(y=None), depth=2 gate id=51: name=g6, type=and3, value=(y=None), depth=1 gate id=52: name=g7, type=or4, value=(y=None), depth=3
None
.step
method which triggers
the logic function of every logic gate whose inputs are
initialized to boolean values.mux2.step()
for g in mux2.gates:
print(g)
gate id=39: name=x0, type=inp, value=(0), depth=0 gate id=40: name=x1, type=inp, value=(1), depth=0 gate id=41: name=x2, type=inp, value=(0), depth=0 gate id=42: name=x3, type=inp, value=(1), depth=0 gate id=43: name=s1, type=inp, value=(1), depth=0 gate id=44: name=s2, type=inp, value=(0), depth=0 gate id=45: name=y, type=out, value=(None), depth=4 gate id=46: name=g1, type=not, value=(y=0), depth=1 gate id=47: name=g2, type=not, value=(y=1), depth=1 gate id=48: name=g3, type=and3, value=(y=None), depth=2 gate id=49: name=g4, type=and3, value=(y=None), depth=2 gate id=50: name=g5, type=and3, value=(y=None), depth=2 gate id=51: name=g6, type=and3, value=(y=0), depth=1 gate id=52: name=g7, type=or4, value=(y=None), depth=3
None
state.step
method
three more times.mux2.step()
for g in mux2.gates:
print(g)
gate id=39: name=x0, type=inp, value=(0), depth=0 gate id=40: name=x1, type=inp, value=(1), depth=0 gate id=41: name=x2, type=inp, value=(0), depth=0 gate id=42: name=x3, type=inp, value=(1), depth=0 gate id=43: name=s1, type=inp, value=(1), depth=0 gate id=44: name=s2, type=inp, value=(0), depth=0 gate id=45: name=y, type=out, value=(None), depth=4 gate id=46: name=g1, type=not, value=(y=0), depth=1 gate id=47: name=g2, type=not, value=(y=1), depth=1 gate id=48: name=g3, type=and3, value=(y=0), depth=2 gate id=49: name=g4, type=and3, value=(y=0), depth=2 gate id=50: name=g5, type=and3, value=(y=0), depth=2 gate id=51: name=g6, type=and3, value=(y=0), depth=1 gate id=52: name=g7, type=or4, value=(y=None), depth=3
mux2.step()
for g in mux2.gates:
print(g)
gate id=39: name=x0, type=inp, value=(0), depth=0 gate id=40: name=x1, type=inp, value=(1), depth=0 gate id=41: name=x2, type=inp, value=(0), depth=0 gate id=42: name=x3, type=inp, value=(1), depth=0 gate id=43: name=s1, type=inp, value=(1), depth=0 gate id=44: name=s2, type=inp, value=(0), depth=0 gate id=45: name=y, type=out, value=(None), depth=4 gate id=46: name=g1, type=not, value=(y=0), depth=1 gate id=47: name=g2, type=not, value=(y=1), depth=1 gate id=48: name=g3, type=and3, value=(y=0), depth=2 gate id=49: name=g4, type=and3, value=(y=0), depth=2 gate id=50: name=g5, type=and3, value=(y=0), depth=2 gate id=51: name=g6, type=and3, value=(y=0), depth=1 gate id=52: name=g7, type=or4, value=(y=0), depth=3
mux2.step()
for g in mux2.gates:
print(g)
gate id=39: name=x0, type=inp, value=(0), depth=0 gate id=40: name=x1, type=inp, value=(1), depth=0 gate id=41: name=x2, type=inp, value=(0), depth=0 gate id=42: name=x3, type=inp, value=(1), depth=0 gate id=43: name=s1, type=inp, value=(1), depth=0 gate id=44: name=s2, type=inp, value=(0), depth=0 gate id=45: name=y, type=out, value=(0), depth=4 gate id=46: name=g1, type=not, value=(y=0), depth=1 gate id=47: name=g2, type=not, value=(y=1), depth=1 gate id=48: name=g3, type=and3, value=(y=0), depth=2 gate id=49: name=g4, type=and3, value=(y=0), depth=2 gate id=50: name=g5, type=and3, value=(y=0), depth=2 gate id=51: name=g6, type=and3, value=(y=0), depth=1 gate id=52: name=g7, type=or4, value=(y=0), depth=3
o = mux2.get()
print(o)
y=0
o = mux2(a)
print("Input:", a)
print("Output:", o)
Input: x3=1, x2=0, x1=1, x0=0, s2=0, s1=1 Output: y=0
mux2
serves as a function too! o = mux2(a)
we perform all the
follwoing thingsa
.step()
method repeatedly
in order to reach the output gates .get
method.run()
method for performing
the computation, and the use the get()
method for obtaining
the circuit output# Obtain handles to MUX2 input gates
x0 = mux2["x0"] # mux2["x0"] is a handle to gate "x0"
x1 = mux2["x1"]
x2 = mux2["x2"]
x3 = mux2["x3"]
s1 = mux2["s1"]
s2 = mux2["s2"]
# Now we have handles to all MUX2 input gates, we can do
# all kind of things with them:
x0.set(0)
x1.set(0)
x2.set(1)
x3.set(1)
s1.set(1)
s2.set(0)
mux2.run() # Running the circuit
o = mux2.get() # Obtaining the output
print("The output is:")
print(o)
The output is: y=0
mux2["x0"].set(0) # mux2["x0"] is a handle to gate "x0"
mux2["x1"].set(0)
mux2["x2"].set(1)
mux2["x3"].set(1)
mux2["s1"].set(1)
mux2["s2"].set(0)
mux2.run() # Running the circuit
o = mux2.get() # Obtaining the output
print("The output is:")
print(o)
The output is: y=0
y=0
.mux2.set(a)
mux2.run()
o = mux2.get()
o = mux2(a)
Input = [x.name for x in ham.input] # we need the names of the inputs
Output = [y.name for y in ham.output] # we also want to print the output names
print("Input:", Input)
print("Output:", Output)
for a in Assign.iter(Input):
o = ham(a)
print("ham: %s => %s" % (a.bits(), o.bits()))
Input: ['x1', 'x2', 'x3', 'x4'] Output: ['y1', 'y2', 'y3'] ham: 0000 => 000 ham: 0001 => 101 ham: 0010 => 000 ham: 0011 => 100 ham: 0100 => 111 ham: 0101 => 010 ham: 0110 => 111 ham: 0111 => 011 ham: 1000 => 000 ham: 1001 => 101 ham: 1010 => 000 ham: 1011 => 000 ham: 1100 => 111 ham: 1101 => 010 ham: 1110 => 011 ham: 1111 => 111
Input = [x.name for x in ham.input]
Output = [y.name for y in ham.output]
names = Input + Output
head = " ".join(names)
print(head)
for a in Assign.iter(Input):
o = ham(a)
bits = tuple(a() + o())
line = len(bits) * "%d " % bits
print(line)
x1 x2 x3 x4 y1 y2 y3 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0 0 1 0 0 0 0 0 0 1 1 1 0 0 0 1 0 0 1 1 1 0 1 0 1 0 1 0 0 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 0 0 0 0 0 1 0 0 1 1 0 1 1 0 1 0 0 0 0 1 0 1 1 0 0 0 1 1 0 0 1 1 1 1 1 0 1 0 1 0 1 1 1 0 0 1 1 1 1 1 1 1 1 1
# Check if two logic circuites are equivalent
# that is: have identical truth tables.
def is_equiv(circ1, circ2):
Inp1 = [g.name for g in circ1.input]
Inp2 = [g.name for g in circ2.input]
if not len(Inp1) == len(Inp2):
return False
for a in Assign.iter(Inp1):
bits1 = circ1(a).bits()
bits2 = circ2(a).bits()
if not bits1 == bits2:
print("a=%s, out1=%s, out2=%s" % (a, bits1, bits2))
return False
return True
load("mux2")
load("mux2b")
mux2 = PyCirc["mux2"]
mux2b = PyCirc["mux2b"]
print("Checking equivalence:")
is_equiv(mux2, mux2b)
path = ['c:/eda/pycirc/lib', 'd:/cmfl/code/logcirc/lib1', 'https://samyzaf.com/pycirc/lib'] Cell = mux2: Validity check: OK. Loaded circuit mux2 from: https://samyzaf.com/pycirc/lib/mux2.py path = ['c:/eda/pycirc/lib', 'd:/cmfl/code/logcirc/lib1', 'https://samyzaf.com/pycirc/lib'] Cell = mux2b: Validity check: OK. Loaded circuit mux2b from: https://samyzaf.com/pycirc/lib/mux2b.py Checking equivalence:
True
We also need one instance of 2x1 Multiplexer (aka MUX1), which we leave to the student as an easy exercise.
need("mux2")
Define("mux3")
GATE("x<0:7>; s<1:3>", type="inp")
GATE("y", type="out")
GATE("g1", type="mux2")
GATE("g2", type="mux2")
GATE("g3", type="mux1")
WIRE("x0", "g1/x0")
WIRE("x1", "g1/x1")
WIRE("x2", "g1/x2")
WIRE("x3", "g1/x3")
WIRE("x4", "g2/x0")
WIRE("x5", "g2/x1")
WIRE("x6", "g2/x2")
WIRE("x7", "g2/x3")
WIRE("s2", "g1/s1")
WIRE("s2", "g2/s1")
WIRE("s3", "g1/s2")
WIRE("s3", "g2/s2")
WIRE("s1", "g3/s1")
WIRE("g1/y", "g3/x0")
WIRE("g2/y", "g3/x1")
WIRE("g3/y", "y")
EndDef()
Cell = mux3: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f011d409290>
mux3 = PyCirc["mux3"]
for g in mux3.gates:
print(g)
gate id=81: name=x0, type=inp, value=(None), depth=0 gate id=82: name=x1, type=inp, value=(None), depth=0 gate id=83: name=x2, type=inp, value=(None), depth=0 gate id=84: name=x3, type=inp, value=(None), depth=0 gate id=85: name=x4, type=inp, value=(None), depth=0 gate id=86: name=x5, type=inp, value=(None), depth=0 gate id=87: name=x6, type=inp, value=(None), depth=0 gate id=88: name=x7, type=inp, value=(None), depth=0 gate id=89: name=s1, type=inp, value=(None), depth=0 gate id=90: name=s2, type=inp, value=(None), depth=0 gate id=91: name=s3, type=inp, value=(None), depth=0 gate id=92: name=y, type=out, value=(None), depth=3 gate id=93: name=g1, type=mux2, value=(y=None), depth=1 gate id=94: name=g2, type=mux2, value=(y=None), depth=1 gate id=95: name=g3, type=mux1, value=(y=None), depth=2
mux3 = PyCirc["mux3"]
for g in mux3.gates:
print("%s = %s" % (g.name, g.get()))
x0 = None x1 = None x2 = None x3 = None x4 = None x5 = None x6 = None x7 = None s1 = None s2 = None s3 = None y = None g1 = y=None g2 = y=None g3 = y=None
for w in mux3.wires:
print(w)
wire id=102: source=gate id=81: name=x0, type=inp, value=(None), depth=0 target=gate id=93: name=g1, type=mux2, value=(y=None), depth=1 source=None target=x0 wire id=103: source=gate id=82: name=x1, type=inp, value=(None), depth=0 target=gate id=93: name=g1, type=mux2, value=(y=None), depth=1 source=None target=x1 wire id=104: source=gate id=83: name=x2, type=inp, value=(None), depth=0 target=gate id=93: name=g1, type=mux2, value=(y=None), depth=1 source=None target=x2 wire id=105: source=gate id=84: name=x3, type=inp, value=(None), depth=0 target=gate id=93: name=g1, type=mux2, value=(y=None), depth=1 source=None target=x3 wire id=106: source=gate id=85: name=x4, type=inp, value=(None), depth=0 target=gate id=94: name=g2, type=mux2, value=(y=None), depth=1 source=None target=x0 wire id=107: source=gate id=86: name=x5, type=inp, value=(None), depth=0 target=gate id=94: name=g2, type=mux2, value=(y=None), depth=1 source=None target=x1 wire id=108: source=gate id=87: name=x6, type=inp, value=(None), depth=0 target=gate id=94: name=g2, type=mux2, value=(y=None), depth=1 source=None target=x2 wire id=109: source=gate id=88: name=x7, type=inp, value=(None), depth=0 target=gate id=94: name=g2, type=mux2, value=(y=None), depth=1 source=None target=x3 wire id=110: source=gate id=90: name=s2, type=inp, value=(None), depth=0 target=gate id=93: name=g1, type=mux2, value=(y=None), depth=1 source=None target=s1 wire id=111: source=gate id=90: name=s2, type=inp, value=(None), depth=0 target=gate id=94: name=g2, type=mux2, value=(y=None), depth=1 source=None target=s1 wire id=112: source=gate id=91: name=s3, type=inp, value=(None), depth=0 target=gate id=93: name=g1, type=mux2, value=(y=None), depth=1 source=None target=s2 wire id=113: source=gate id=91: name=s3, type=inp, value=(None), depth=0 target=gate id=94: name=g2, type=mux2, value=(y=None), depth=1 source=None target=s2 wire id=114: source=gate id=89: name=s1, type=inp, value=(None), depth=0 target=gate id=95: name=g3, type=mux1, value=(y=None), depth=2 source=None target=s1 wire id=115: source=gate id=93: name=g1, type=mux2, value=(y=None), depth=1 target=gate id=95: name=g3, type=mux1, value=(y=None), depth=2 source=y target=x0 wire id=116: source=gate id=94: name=g2, type=mux2, value=(y=None), depth=1 target=gate id=95: name=g3, type=mux1, value=(y=None), depth=2 source=y target=x1 wire id=117: source=gate id=95: name=g3, type=mux1, value=(y=None), depth=2 target=gate id=92: name=y, type=out, value=(None), depth=3 source=y target=None
for w in mux3.wires:
print("%s => %s" % (w.source, w.target))
x0 => g1/x0 x1 => g1/x1 x2 => g1/x2 x3 => g1/x3 x4 => g2/x0 x5 => g2/x1 x6 => g2/x2 x7 => g2/x3 s2 => g1/s1 s2 => g2/s1 s3 => g1/s2 s3 => g2/s2 s1 => g3/s1 g1/y => g3/x0 g2/y => g3/x1 g3/y => y
a = Assign("x<0:7> ; s<1:3>", "00001000" + "000")
mux3.set(a)
for x in mux3.input:
print("%s = %d" % (x.name, x.value))
g3 = mux3["g3"]
for g in mux3.in_gates(g3):
print(g)
print("Depth =", mux3.depth)
x0 = 0 x1 = 0 x2 = 0 x3 = 0 x4 = 1 x5 = 0 x6 = 0 x7 = 0 s1 = 0 s2 = 0 s3 = 0 gate id=89: name=s1, type=inp, value=(0), depth=0 gate id=93: name=g1, type=mux2, value=(y=None), depth=1 gate id=94: name=g2, type=mux2, value=(y=None), depth=1 Depth = 3
a = Assign("x<0:7> ; s<1:3>", "00000011" + "111")
print("Input:")
print(a)
o = mux3(a)
print("Output:")
print(o)
Input: x0=0, x1=0, x2=0, x3=0, x4=0, x5=0, x6=1, x7=1, s1=1, s2=1, s3=1 Output: y=1
Here is a simple design for 3 bits adder with carry in (cin) and carry out (cout) bits
cin
.# ADDER3
# Input: a2, a1, a0, b2, b1, b0, cin
# Output: y2, y1, y0, cout
Define("adder3")
GATE("a2", type="inp")
GATE("a1", type="inp")
GATE("a0", type="inp")
GATE("b2", type="inp")
GATE("b1", type="inp")
GATE("b0", type="inp")
GATE("cin", type="inp")
GATE("y2", type="out")
GATE("y1", type="out")
GATE("y0", type="out")
GATE("cout", type="out")
GATE("g1", type="xor2")
GATE("g2", type="xor2")
GATE("g3", type="xor2")
GATE("g4", type="mux1")
GATE("g5", type="mux1")
GATE("g6", type="mux1")
GATE("g7", type="xor2")
GATE("g8", type="xor2")
GATE("g9", type="xor2")
WIRE("a2", "g3/x2")
WIRE("a2", "g6/x0")
WIRE("a1", "g2/x2")
WIRE("a0", "g1/x2")
WIRE("a0", "g4/x0")
WIRE("b2", "g3/x1")
WIRE("b1", "g2/x1")
WIRE("b1", "g5/x0")
WIRE("b0", "g4/x1")
WIRE("b0", "g9/x2")
WIRE("cin", "g1/x1")
WIRE("g1/y", "g4/s1")
WIRE("g1/y", "g9/x1")
WIRE("g2/y", "g5/s1")
WIRE("g2/y", "g8/x2")
WIRE("g3/y", "g6/s1")
WIRE("g3/y", "g7/x2")
WIRE("g4/y", "g5/x1")
WIRE("g4/y", "g8/x1")
WIRE("g5/y", "g6/x1")
WIRE("g5/y", "g7/x1")
WIRE("g6/y", "cout")
WIRE("g7/y", "y2")
WIRE("g8/y", "y1")
WIRE("g9/y", "y0")
EndDef()
Cell = adder3: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f011d8faa90>
# ADDER3, compressed version
# Input: a2, a1, a0, b2, b1, b0, cin
# Output: y2, y1, y0, cout
Define("adder3")
GATE("a<2:0>", type="inp")
GATE("b<2:0>", type="inp")
GATE("cin", type="inp")
GATE("y<2:0>", type="out")
GATE("cout", type="out")
GATE("g<1:3,7:9>", type="xor2")
GATE("g<4:6>", type="mux1")
WIRE("a2", "g3/x2; g6/x0")
WIRE("a1", "g2/x2")
WIRE("a0", "g1/x2; g4/x0")
WIRE("b2", "g3/x1")
WIRE("b1", "g2/x1; g5/x0")
WIRE("b0", "g4/x1; g9/x2")
WIRE("cin", "g1/x1")
WIRE("g1/y", "g4/s1; g9/x1")
WIRE("g2/y", "g5/s1; g8/x2")
WIRE("g3/y", "g6/s1; g7/x2")
WIRE("g4/y", "g5/x1; g8/x1")
WIRE("g5/y", "g6/x1; g7/x1")
WIRE("g6/y", "cout")
WIRE("g<7:9>/y", "y<2:0>")
EndDef()
Cell = adder3: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f011d423c90>
adder3 = PyCirc["adder3"]
011+011=110
bits = "011" + "011" + "0"
a = Assign("a<2:0>; b<2:0>; cin", bits)
o = adder3(a)
cin = a["cin"]
cout = o["cout"]
A = a.bits("a<2:0>")
B = a.bits("b<2:0>")
Y = o.bits("y<2:0>")
print("%s + %s = %s : cin=%s cout=%s" % (A,B,Y,cin,cout))
011 + 011 = 110 : cin=0 cout=0
a<8:0> + b<8:0> + cin
Output: y<8:0> + cout
# ADDER9
# Input: a8, a7, a6, a5, a4, a3, a2, a1, a0, b8, b7, b6, b5, b4, b3, b2, b1, b0, cin
# Output: y8, y7, y6, y5, y4, y3, y2, y1, y0, cout
need("adder3")
Define("adder9")
GATE("a<8:0>;b<8:0>", type="inp")
GATE("cin", type="inp")
GATE("y<8:0>", type="out")
GATE("cout", type="out")
GATE("g1", type="adder3") # First ADDER3 gate
GATE("g2", type="adder3") # Second ADDER3 gate
GATE("g3", type="adder3") # Third ADDER3 gate
WIRE("a<0:2>", "g1/a<0:2>"),
WIRE("a<3:5>", "g2/a<0:2>"),
WIRE("a<6:8>", "g3/a<0:2>"),
WIRE("b<0:2>", "g1/b<0:2>"),
WIRE("b<3:5>", "g2/b<0:2>"),
WIRE("b<6:8>", "g3/b<0:2>"),
WIRE("cin", "g1/cin"),
WIRE("g1/cout", "g2/cin"),
WIRE("g2/cout", "g3/cin"),
WIRE("g3/cout", "cout"),
WIRE("g1/y<0:2>", "y<0:2>"),
WIRE("g2/y<0:2>", "y<3:5>"),
WIRE("g3/y<0:2>", "y<6:8>"),
EndDef()
Cell = adder9: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f011d8e7e10>
full_run
utility can be used for traversing all input/output pairs.adder9 = PyCirc["adder9"]
full_run(adder9)
Input: a8=0, a7=0, a6=0, a5=0, a4=0, a3=0, a2=0, a1=0, a0=0, b8=0, b7=0, b6=0, b5=0, b4=0, b3=0, b2=0, b1=0, b0=0, cin=0 Output: y8=0, y7=0, y6=0, y5=0, y4=0, y3=0, y2=0, y1=0, y0=0, cout=0 Press <Enter> to continue or 'q' to quit Next? Input: a8=0, a7=0, a6=0, a5=0, a4=0, a3=0, a2=0, a1=0, a0=0, b8=0, b7=0, b6=0, b5=0, b4=0, b3=0, b2=0, b1=0, b0=0, cin=1 Output: y8=0, y7=0, y6=0, y5=0, y4=0, y3=0, y2=0, y1=0, y0=1, cout=0 Press <Enter> to continue or 'q' to quit Next? Input: a8=0, a7=0, a6=0, a5=0, a4=0, a3=0, a2=0, a1=0, a0=0, b8=0, b7=0, b6=0, b5=0, b4=0, b3=0, b2=0, b1=0, b0=1, cin=0 Output: y8=0, y7=0, y6=0, y5=0, y4=0, y3=0, y2=0, y1=0, y0=1, cout=0 Press <Enter> to continue or 'q' to quit Next? q
def random_assignment(names):
names = expand(names)
a = Assign(names)
for x in names:
a[x] = randint(0,1)
return a
names = "a<8:0>; b<8:0>; cin"
for i in range(5):
a = random_assignment(names)
A = a.bits("a<8:0>")
B = a.bits("b<8:0>")
print(A, B, a["cin"])
000000001 000110011 0 000110000 010110001 1 111011011 100000010 0 111000011 001100110 1 011111111 111000001 1
names = "a<8:0>; b<8:0>; cin"
while True:
a = random_assignment(names)
A = a.bits("a<8:0>")
B = a.bits("b<8:0>")
cin = a["cin"]
print(80*"-")
print("Input: %s + %s + %s" % (A, B, cin))
o = adder9(a)
Y = o.bits("y<8:0>")
cout = o["cout"]
print("Output: %s + %s" % (Y, cout))
print("Verify sum: %s" % (bin(int(A,2) + int(B,2)),))
inpstr = input("Press <Enter> to continue or q to stop: ")
if "q" == inpstr:
break
-------------------------------------------------------------------------------- Input: 111001010 + 000010011 + 1 Output: 111011110 + 0 Verify sum: 0b111011101 Press <Enter> to continue or q to stop: -------------------------------------------------------------------------------- Input: 110110111 + 011010010 + 1 Output: 010001010 + 1 Verify sum: 0b1010001001 Press <Enter> to continue or q to stop: -------------------------------------------------------------------------------- Input: 000101011 + 000111000 + 0 Output: 001100011 + 0 Verify sum: 0b1100011 Press <Enter> to continue or q to stop: q
# CELL: FULL ADDER 2
# We need adder3
need("adder3")
# Here we present a different style for defining a logic circuit
gates = (
GATE("a1", type="inp")
+ GATE("a0", type="inp")
+ GATE("b1", type="inp")
+ GATE("b0", type="inp")
+ GATE("ad3", type="adder3")
+ GATE("y2", type="out")
+ GATE("y1", type="out")
+ GATE("y0", type="out")
+ GATE("g0", type="zero")
)
wires = [
Wire("a0", "ad3/a0"),
Wire("a1", "ad3/a1"),
Wire("b0", "ad3/b0"),
Wire("b1", "ad3/b1"),
Wire("g0", "ad3/a2"),
Wire("g0", "ad3/b2"),
Wire("g0", "ad3/cin"),
Wire("ad3/y0", "y0"),
Wire("ad3/y1", "y1"),
Wire("ad3/y2", "y2"),
]
fa2 = PyCirc("full_adder2", gates, wires)
pycircLib.add_circ(fa2)
ATTENTION: some outputs are dangling! ad3 (adder3) : {'cout'} Cell = full_adder2: Validity check: OK.
<pycirc.pycirc.Cell at 0x7f011d39ed10>
Wire
class instead
of the higher level WIRE
function.Wire
class is suited for generating a single wire
object while WIERE
generates multiple wires and supports
compressed names. g0
which fixes the carry in (cin) to a constant 0 value.A "dangling output" allert means that an output is not connected to anything.
cout
pin of ADDER3.def AND(a, output="y"):
o = Assign(output)
for x in a:
if a[x] == 0:
for y in o: o[y] = 0
return o
for y in o: o[y] = 1
return o
def OR(a, output="y"):
o = Assign(output)
for x in a:
if a[x] == 1:
for y in o: o[y] = 1
return o
for y in o: o[y] = 0
return o
def NOR(a, output="y"):
o = Assign(output)
o1 = OR(a, Assign("y"))
b = Assign("x", o1["y"])
return NOT(b, o)
def XOR(a, output="y"):
o = Assign(output)
if sum(a[x] for x in a) == 1:
for y in o: o[y] = 1
return o
else:
for y in o: o[y] = 0
return o
a
has?# Count the number of 1-bits in the assignment a
def COUNT_ONES(a):
s = sum(a())
k = len(a).bit_length()
bits = bin(s)[2:].zfill(k)
names = "y<%d:1>" % (k,)
bits = [int(y) for y in bits]
o = Assign(names, bits)
return o
# Magnitude Comparator Operator
# input: "a<0:n>;b<0:n>"
# output: "y<1:3>"
# If a<b returns 100
# If a==b returns 010
# If a>b returns 001
def COMPARE(a):
A = []
B = []
for name in a:
if name[0] == "a":
A.append(name)
else:
B.append(name)
A = int(a.bits(A), 2)
B = int(a.bits(B), 2)
o = Assign("y<1:3>")
if A<B:
o.assign("100")
elif A==B:
o.assign("010")
else:
o.assign("001")
return o
a = Assign("x<1:5>", "01011")
o = COUNT_ONES(a)
print(o)
y3=0, y2=1, y1=1
a = Assign("x<1:8>", "11101011")
print("Input:", a.bits())
o = COUNT_ONES(a)
print("Output (binary):", o.bits())
print("Output (decimal):", int(o.bits(), 2))
Input: 11101011 Output (binary): 0110 Output (decimal): 6
def ZERO(a=None, output="y"):
o = Assign(output)
for y in o: o[y] = 0
return o
def ONE(a=None, output="y"):
o = Assign(output)
for y in o: o[y] = 1
return o
a = Assign("a<0:7>; b<0:7>", "00101101" + "00110101")
o = COMPARE(a)
print(o)
y1=1, y2=0, y3=0
Cell
Class¶For example, here is a high level hierarchical view of Intel's Skylake cpu
Cell
class.count3 = Cell("count3", operator=COUNT_ONES, input="x<1:3>", output="y<1:2>", depth=3)
# currently all box type Cells are stored in the default PyCirc library called pycircLib
pycircLib.add(count3)
As an example, let's define a COUNT6 circuit by using two gates of type COUNT3.
Define("count6")
GATE("x<1:6>", type="inp")
GATE("y<1:3>", type="out")
GATE("c1", type="count3") # Gate "c1" of type count3
GATE("c2", type="count3") # Gate "c2" of type count3
GATE("a", type="full_adder2")
WIRE("x<1:3>", "c1/x<1:3>")
WIRE("x<4:6>", "c2/x<1:3>")
WIRE("c1/y1", "a/a1")
WIRE("c1/y2", "a/a0")
WIRE("c2/y1", "a/b1")
WIRE("c2/y2", "a/b0")
WIRE("a/y<2:0>", "y<1:3>")
EndDef()
Cell = count6: Validity check: OK.
<pycirc.pycirc.PyCirc at 0x7f011d396b50>
See the factory module in the PyCirc package for more examples.
pycircLib.add_box(name="and2", operator=AND, input="x<1:2>", output=["y"])
pycircLib.add_box(name="and3", operator=AND, input="x<1:3>", output=["y"])
pycircLib.add_box(name="and4", operator=AND, input="x<1:4>", output=["y"])
pycircLib.add_box(name="and5", operator=AND, input="x<1:5>", output=["y"])
pycircLib.add_box(name="and6", operator=AND, input="x<1:6>", output=["y"])
pycircLib.add_box(name="and7", operator=AND, input="x<1:7>", output=["y"])
pycircLib.add_box(name="and8", operator=AND, input="x<1:8>", output=["y"])
We can also use Python loops to add box cells to our cell library.
The following Python loop, adds 32 box cellls to the PyCirc cell library
for k in range(2,9):
inp = "x<1:%s>" % (k,)
name = "or" + str(k)
pycircLib.add_box(name, operator=OR, input=inp, output=["y"])
name = "xor" + str(k)
pycircLib.add_box(name, operator=XOR, input=inp, output=["y"])
name = "nor" + str(k)
pycircLib.add_box(name, operator=NOR, input=inp, output=["y"])
name = "nand" + str(k)
pycircLib.add_box(name, operator=NAND, input=inp, output=["y"])
from IPython.display import HTML
from urllib.request import urlopen
css = urlopen("https://samyzaf.com/css/pycirc.css").read().decode('utf-8')
HTML('<style>{}</style>'.format(css))