Link to the the Google Colaboratory notebook
pip install pycirchdl
# To install from this notebook, run this cell.
%pip install pycirchdl
# This should also work:
# !pip install --upgrade pycirchdl
# 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 pycirchdl
# or
# !pip uninstall pycirchdl
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Collecting pycirchdl Downloading pycirchdl-1.1.tar.gz (10 kB) Requirement already satisfied: networkx in /usr/local/lib/python3.7/dist-packages (from pycirchdl) (2.6.3) Building wheels for collected packages: pycirchdl Building wheel for pycirchdl (setup.py) ... done Created wheel for pycirchdl: filename=pycirchdl-1.1-py3-none-any.whl size=10719 sha256=e60de54a495c6c50568ffdea2d99d824ae48efd995e3c9c2b48dfe9149ed7a5e Stored in directory: /root/.cache/pip/wheels/8f/7b/92/12e0c75c8ac5545cf88a6507a42ed4bf8bc25adcf25ff0d2e8 Successfully built pycirchdl Installing collected packages: pycirchdl Successfully installed pycirchdl-1.1
from pycirchdl 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.
<pycirchdl.pycirc.PyCirc at 0x7fa21a07b490>
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.
<pycirchdl.pycirc.PyCirc at 0x7fa21a07bd50>
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.
<pycirchdl.pycirc.PyCirc at 0x7fa21a02dc50>
"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")
# File: mux2.py (compressed version!)
# 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")
# get a grip on our MUX2 object
mux2 = load("mux2")
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
for g in mux2.gates:
print(g)
gate id=25: name=x0, type=inp, value=(None), depth=0 gate id=26: name=x1, type=inp, value=(None), depth=0 gate id=27: name=x2, type=inp, value=(None), depth=0 gate id=28: name=x3, type=inp, value=(None), depth=0 gate id=29: name=s1, type=inp, value=(None), depth=0 gate id=30: name=s2, type=inp, value=(None), depth=0 gate id=31: name=y, type=out, value=(None), depth=4 gate id=32: name=g1, type=not, value=(y=None), depth=1 gate id=33: name=g2, type=not, value=(y=None), depth=1 gate id=34: name=g3, type=and3, value=(y=None), depth=2 gate id=35: name=g4, type=and3, value=(y=None), depth=2 gate id=36: name=g5, type=and3, value=(y=None), depth=2 gate id=37: name=g6, type=and3, value=(y=None), depth=1 gate id=38: 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=25: name=x0, type=inp, value=(None), depth=0 gate id=26: name=x1, type=inp, value=(None), depth=0 gate id=27: name=x2, type=inp, value=(None), depth=0 gate id=28: name=x3, type=inp, value=(None), depth=0 gate id=29: name=s1, type=inp, value=(None), depth=0 gate id=30: name=s2, type=inp, value=(None), depth=0 gate id=31: name=y, type=out, value=(None), depth=4 gate id=32: name=g1, type=not, value=(y=None), depth=1 gate id=33: name=g2, type=not, value=(y=None), depth=1 gate id=34: name=g3, type=and3, value=(y=None), depth=2 gate id=35: name=g4, type=and3, value=(y=None), depth=2 gate id=36: name=g5, type=and3, value=(y=None), depth=2 gate id=37: name=g6, type=and3, value=(y=None), depth=1 gate id=38: 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=25: name=x0, type=inp, value=(0), depth=0 gate id=26: name=x1, type=inp, value=(1), depth=0 gate id=27: name=x2, type=inp, value=(0), depth=0 gate id=28: name=x3, type=inp, value=(1), depth=0 gate id=29: name=s1, type=inp, value=(1), depth=0 gate id=30: name=s2, type=inp, value=(0), depth=0 gate id=31: name=y, type=out, value=(None), depth=4 gate id=32: name=g1, type=not, value=(y=None), depth=1 gate id=33: name=g2, type=not, value=(y=None), depth=1 gate id=34: name=g3, type=and3, value=(y=None), depth=2 gate id=35: name=g4, type=and3, value=(y=None), depth=2 gate id=36: name=g5, type=and3, value=(y=None), depth=2 gate id=37: name=g6, type=and3, value=(y=None), depth=1 gate id=38: 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=25: name=x0, type=inp, value=(0), depth=0 gate id=26: name=x1, type=inp, value=(1), depth=0 gate id=27: name=x2, type=inp, value=(0), depth=0 gate id=28: name=x3, type=inp, value=(1), depth=0 gate id=29: name=s1, type=inp, value=(1), depth=0 gate id=30: name=s2, type=inp, value=(0), depth=0 gate id=31: name=y, type=out, value=(None), depth=4 gate id=32: name=g1, type=not, value=(y=0), depth=1 gate id=33: name=g2, type=not, value=(y=1), depth=1 gate id=34: name=g3, type=and3, value=(y=None), depth=2 gate id=35: name=g4, type=and3, value=(y=None), depth=2 gate id=36: name=g5, type=and3, value=(y=None), depth=2 gate id=37: name=g6, type=and3, value=(y=0), depth=1 gate id=38: 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=25: name=x0, type=inp, value=(0), depth=0 gate id=26: name=x1, type=inp, value=(1), depth=0 gate id=27: name=x2, type=inp, value=(0), depth=0 gate id=28: name=x3, type=inp, value=(1), depth=0 gate id=29: name=s1, type=inp, value=(1), depth=0 gate id=30: name=s2, type=inp, value=(0), depth=0 gate id=31: name=y, type=out, value=(None), depth=4 gate id=32: name=g1, type=not, value=(y=0), depth=1 gate id=33: name=g2, type=not, value=(y=1), depth=1 gate id=34: name=g3, type=and3, value=(y=0), depth=2 gate id=35: name=g4, type=and3, value=(y=0), depth=2 gate id=36: name=g5, type=and3, value=(y=0), depth=2 gate id=37: name=g6, type=and3, value=(y=0), depth=1 gate id=38: name=g7, type=or4, value=(y=None), depth=3
mux2.step()
for g in mux2.gates:
print(g)
gate id=25: name=x0, type=inp, value=(0), depth=0 gate id=26: name=x1, type=inp, value=(1), depth=0 gate id=27: name=x2, type=inp, value=(0), depth=0 gate id=28: name=x3, type=inp, value=(1), depth=0 gate id=29: name=s1, type=inp, value=(1), depth=0 gate id=30: name=s2, type=inp, value=(0), depth=0 gate id=31: name=y, type=out, value=(None), depth=4 gate id=32: name=g1, type=not, value=(y=0), depth=1 gate id=33: name=g2, type=not, value=(y=1), depth=1 gate id=34: name=g3, type=and3, value=(y=0), depth=2 gate id=35: name=g4, type=and3, value=(y=0), depth=2 gate id=36: name=g5, type=and3, value=(y=0), depth=2 gate id=37: name=g6, type=and3, value=(y=0), depth=1 gate id=38: name=g7, type=or4, value=(y=0), depth=3
mux2.step()
for g in mux2.gates:
print(g)
gate id=25: name=x0, type=inp, value=(0), depth=0 gate id=26: name=x1, type=inp, value=(1), depth=0 gate id=27: name=x2, type=inp, value=(0), depth=0 gate id=28: name=x3, type=inp, value=(1), depth=0 gate id=29: name=s1, type=inp, value=(1), depth=0 gate id=30: name=s2, type=inp, value=(0), depth=0 gate id=31: name=y, type=out, value=(0), depth=4 gate id=32: name=g1, type=not, value=(y=0), depth=1 gate id=33: name=g2, type=not, value=(y=1), depth=1 gate id=34: name=g3, type=and3, value=(y=0), depth=2 gate id=35: name=g4, type=and3, value=(y=0), depth=2 gate id=36: name=g5, type=and3, value=(y=0), depth=2 gate id=37: name=g6, type=and3, value=(y=0), depth=1 gate id=38: 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
mux2 = load("mux2")
mux2b = load("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.
<pycirchdl.pycirc.PyCirc at 0x7fa219fe5a10>
mux3 = PyCirc["mux3"]
for g in mux3.gates:
print(g)
gate id=67: name=x0, type=inp, value=(None), depth=0 gate id=68: name=x1, type=inp, value=(None), depth=0 gate id=69: name=x2, type=inp, value=(None), depth=0 gate id=70: name=x3, type=inp, value=(None), depth=0 gate id=71: name=x4, type=inp, value=(None), depth=0 gate id=72: name=x5, type=inp, value=(None), depth=0 gate id=73: name=x6, type=inp, value=(None), depth=0 gate id=74: name=x7, type=inp, value=(None), depth=0 gate id=75: name=s1, type=inp, value=(None), depth=0 gate id=76: name=s2, type=inp, value=(None), depth=0 gate id=77: name=s3, type=inp, value=(None), depth=0 gate id=78: name=y, type=out, value=(None), depth=3 gate id=79: name=g1, type=mux2, value=(y=None), depth=1 gate id=80: name=g2, type=mux2, value=(y=None), depth=1 gate id=81: 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=83: source=gate id=67: name=x0, type=inp, value=(None), depth=0 target=gate id=79: name=g1, type=mux2, value=(y=None), depth=1 source=None target=x0 wire id=84: source=gate id=68: name=x1, type=inp, value=(None), depth=0 target=gate id=79: name=g1, type=mux2, value=(y=None), depth=1 source=None target=x1 wire id=85: source=gate id=69: name=x2, type=inp, value=(None), depth=0 target=gate id=79: name=g1, type=mux2, value=(y=None), depth=1 source=None target=x2 wire id=86: source=gate id=70: name=x3, type=inp, value=(None), depth=0 target=gate id=79: name=g1, type=mux2, value=(y=None), depth=1 source=None target=x3 wire id=87: source=gate id=71: name=x4, type=inp, value=(None), depth=0 target=gate id=80: name=g2, type=mux2, value=(y=None), depth=1 source=None target=x0 wire id=88: source=gate id=72: name=x5, type=inp, value=(None), depth=0 target=gate id=80: name=g2, type=mux2, value=(y=None), depth=1 source=None target=x1 wire id=89: source=gate id=73: name=x6, type=inp, value=(None), depth=0 target=gate id=80: name=g2, type=mux2, value=(y=None), depth=1 source=None target=x2 wire id=90: source=gate id=74: name=x7, type=inp, value=(None), depth=0 target=gate id=80: name=g2, type=mux2, value=(y=None), depth=1 source=None target=x3 wire id=91: source=gate id=76: name=s2, type=inp, value=(None), depth=0 target=gate id=79: name=g1, type=mux2, value=(y=None), depth=1 source=None target=s1 wire id=92: source=gate id=76: name=s2, type=inp, value=(None), depth=0 target=gate id=80: name=g2, type=mux2, value=(y=None), depth=1 source=None target=s1 wire id=93: source=gate id=77: name=s3, type=inp, value=(None), depth=0 target=gate id=79: name=g1, type=mux2, value=(y=None), depth=1 source=None target=s2 wire id=94: source=gate id=77: name=s3, type=inp, value=(None), depth=0 target=gate id=80: name=g2, type=mux2, value=(y=None), depth=1 source=None target=s2 wire id=95: source=gate id=75: name=s1, type=inp, value=(None), depth=0 target=gate id=81: name=g3, type=mux1, value=(y=None), depth=2 source=None target=s1 wire id=96: source=gate id=79: name=g1, type=mux2, value=(y=None), depth=1 target=gate id=81: name=g3, type=mux1, value=(y=None), depth=2 source=y target=x0 wire id=97: source=gate id=80: name=g2, type=mux2, value=(y=None), depth=1 target=gate id=81: name=g3, type=mux1, value=(y=None), depth=2 source=y target=x1 wire id=98: source=gate id=81: name=g3, type=mux1, value=(y=None), depth=2 target=gate id=78: 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=75: name=s1, type=inp, value=(0), depth=0 gate id=79: name=g1, type=mux2, value=(y=None), depth=1 gate id=80: 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.
<pycirchdl.pycirc.PyCirc at 0x7fa219b32050>
# 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.
<pycirchdl.pycirc.PyCirc at 0x7fa219ff73d0>
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.
<pycirchdl.pycirc.PyCirc at 0x7fa219b0ffd0>
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"])
011100101 001100101 0 000001010 001110011 1 101011001 100100100 1 100001010 010110111 0 100111110 110110000 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: 100100000 + 101100101 + 0 Output: 010000101 + 1 Verify sum: 0b1010000101 Press <Enter> to continue or q to stop: -------------------------------------------------------------------------------- Input: 001110100 + 100001101 + 1 Output: 110000010 + 0 Verify sum: 0b110000001 Press <Enter> to continue or q to stop: -------------------------------------------------------------------------------- Input: 111010001 + 111101101 + 1 Output: 110111111 + 1 Verify sum: 0b1110111110 Press <Enter> to continue or q to stop: q
# CELL: ADDER2 - HALF 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"),
]
ha2 = PyCirc("adder2", gates, wires)
pycircLib.add_circ(ha2)
ATTENTION: some outputs are dangling! ad3 (adder3) : {'cout'} Cell = adder2: Validity check: OK.
<pycirchdl.pycirc.Cell at 0x7fa219ab0550>
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="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.
<pycirchdl.pycirc.PyCirc at 0x7fa219ac4e10>
See the factory module in the PyCircHDL 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"])