Link to the the Google Colaboratory notebook
pip install pycircpl
# To install from this notebook, run this cell.
%pip install pycircpl
# This should also work:
# !pip install --upgrade pycircpl
# 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 pycircpl
# or
# !pip uninstall pycircpl
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Collecting pycircpl Downloading pycircpl-1.1.tar.gz (2.2 kB) Building wheels for collected packages: pycircpl Building wheel for pycircpl (setup.py) ... done Created wheel for pycircpl: filename=pycircpl-1.1-py3-none-any.whl size=1915 sha256=c492acc8bc2e5b4611e5d7865ae8a7e9085e6bff315d9cb1aec2cdfe6d0610a3 Stored in directory: /root/.cache/pip/wheels/a3/2d/3b/c702335f765a1d7dfba13c8b049fbcc51bb63fabfe3a5c1938 Successfully built pycircpl Installing collected packages: pycircpl Successfully installed pycircpl-1.1
from pycircpl import *
We start with a very simple logic circuit which we call "FOO" whose PyCirc Diagram is given below
See https://www.samyzaf.com/pycirc/pycirc.html for detailed information on PyCirc diagrams.
Here is the PyCircPl code for modeling this circuit:
def FOO(x1, x2, x3):
g1 = AND(x1, x2)
g2 = NOT(x3)
y1 = g1
y2 = g2
return y1, y2
Notic that this is a pure Python code!
A circuit definition starts with the stabdard Python keyword def
which is used to define a Python function.
FOO
.x1
, x2
, x3
.The function output list (in the last return
statement) is
identical to the circuit output list.
Note that we could make the definition shorter by removing the
two intermediate variable g1
and g2
, but their presence
keeps a resemblance with the circuit diagram.
We ephasize again: the above definition of FOO is a pure Python function!
This means that you can use in any othe Python code and in other programs/scripts for building new circuit functions.
Here is a simple example which computes the circuit for the input
x1 = 1
x2 = 1
x3 = 0
y1,y2 = FOO(1,1,0)
print(f"y1={y1}")
print(f"y2={y2}")
y1=1 y2=1
Here is a more sophisticated example for Pyhton programmers for
generating the Truth Table of FOO
.
That is, all possible outputs.
for x1 in [0,1]:
for x2 in [0,1]:
for x3 in [0,1]:
y1,y2 = FOO(x1,x2,x3)
print(f"FOO({x1},{x2},{x3}) = {y1}, {y2}")
FOO(0,0,0) = 0, 1 FOO(0,0,1) = 0, 0 FOO(0,1,0) = 0, 1 FOO(0,1,1) = 0, 0 FOO(1,0,0) = 0, 1 FOO(1,0,1) = 0, 0 FOO(1,1,0) = 1, 1 FOO(1,1,1) = 1, 0
import
command.FOO
whose function
we defined in Example 1 above.FOO
is used in the function FRED
.def FRED(x1, x2):
g0 = 0
y1,y2 = FOO(x1,x2,g0)
return y1,y2
It also contains one gate of type NOT.
FOO
.# CELL: HAM
# This cell is using the FOO cell which we defined earlier
# "XOR" is a basic function in PyCircPl
def HAM(x1,x2,x3,x4):
g1_y1, g1_y2 = FOO(x1,x3,x2)
g2_y1, g2_y2 = FOO(x3,x4,x4)
g3_y = XOR(g1_y2, g1_y1, g2_y2)
g4_y = NOT(g1_y2)
g5_y = XOR(g1_y2, g2_y1, g2_y2)
y1 = g3_y
y2 = g4_y
y3 = g5_y
return y1, y2, y3
HAM
.product
utility from the itertools
module (which is
automatically loaded by pycircpl) to generate all the possible
combinations of 4 boolean values.for x1,x2,x3,x4 in product([0,1], repeat=4):
y1,y2,y3 = HAM(x1,x2,x3,x4)
print(f"HAM({x1}, {x2}, {x3}, {x4}) = {y1}, {y2}, {y3}")
HAM(0, 0, 0, 0) = 0, 0, 0 HAM(0, 0, 0, 1) = 1, 0, 1 HAM(0, 0, 1, 0) = 0, 0, 0 HAM(0, 0, 1, 1) = 1, 0, 0 HAM(0, 1, 0, 0) = 1, 1, 1 HAM(0, 1, 0, 1) = 0, 1, 0 HAM(0, 1, 1, 0) = 1, 1, 1 HAM(0, 1, 1, 1) = 0, 1, 1 HAM(1, 0, 0, 0) = 0, 0, 0 HAM(1, 0, 0, 1) = 1, 0, 1 HAM(1, 0, 1, 0) = 0, 0, 0 HAM(1, 0, 1, 1) = 0, 0, 0 HAM(1, 1, 0, 0) = 1, 1, 1 HAM(1, 1, 0, 1) = 0, 1, 0 HAM(1, 1, 1, 0) = 0, 1, 1 HAM(1, 1, 1, 1) = 1, 1, 1
def COUNT3(x1, x2, x3):
g1 = AND(x1, x2)
g3 = XOR(x1, x2)
g2 = AND(x3, g3)
g4 = XOR(x3, g3)
g5 = OR(g1, g2)
y1 = g5
y2 = g4
return y1, y2
g1
, g3
, g2
, g4
, g5
, y1
, y2
,
is converted to its corresponding Python statement by simply
applying its logical operator on its inputs.g3
must be defined befor g2
since
it is an input to g2
!COUNT3
on the input list
x1 = 1 ; x2=0 ; x3 = 1
COUNT3(1,0,1)
(1, 0)
(1,0,1)
is 2 which in binary
form is (1,0)
as the above calculation shows.for x1,x2,x3 in product([0,1], repeat=3):
y1,y2 = COUNT3(x1,x2,x3)
print(f"COUNT3({x1},{x2},{x3}) = ({y1}, {y2})")
COUNT3(0,0,0) = (0, 0) COUNT3(0,0,1) = (0, 1) COUNT3(0,1,0) = (0, 1) COUNT3(0,1,1) = (1, 0) COUNT3(1,0,0) = (0, 1) COUNT3(1,0,1) = (1, 0) COUNT3(1,1,0) = (1, 0) COUNT3(1,1,1) = (1, 1)
Here is a simple PyCirc Diagram for a 4x1 Multiplexer circuit (aka MUX2)
# Function for MUX2
# input: x3, x2, x1, x0, s2, s1
# output: y
def MUX2(x0,x1,x2,x3,s1,s2):
g1_y = NOT(s1)
g2_y = NOT(s2)
g3_y = AND(g1_y, x0, g2_y)
g4_y = AND(g1_y, x1, s2)
g5_y = AND(s1, x2, g2_y)
g6_y = AND(s1, x3, s2)
y = OR(g3_y, g4_y, g5_y, g6_y)
return y
print("x0 x1 x2 x3 | s1 s2 | y")
print(30*'-')
for x0,x1,x2,x3,s1,s2 in product([0,1], repeat=6):
y = MUX2(x0,x1,x2,x3,s1,s2)
print(f"{x0} {x1} {x2} {x3} | {s1} {s2} | {y}")
x0 x1 x2 x3 | s1 s2 | y ------------------------------ 0 0 0 0 | 0 0 | 0 0 0 0 0 | 0 1 | 0 0 0 0 0 | 1 0 | 0 0 0 0 0 | 1 1 | 0 0 0 0 1 | 0 0 | 0 0 0 0 1 | 0 1 | 0 0 0 0 1 | 1 0 | 0 0 0 0 1 | 1 1 | 1 0 0 1 0 | 0 0 | 0 0 0 1 0 | 0 1 | 0 0 0 1 0 | 1 0 | 1 0 0 1 0 | 1 1 | 0 0 0 1 1 | 0 0 | 0 0 0 1 1 | 0 1 | 0 0 0 1 1 | 1 0 | 1 0 0 1 1 | 1 1 | 1 0 1 0 0 | 0 0 | 0 0 1 0 0 | 0 1 | 1 0 1 0 0 | 1 0 | 0 0 1 0 0 | 1 1 | 0 0 1 0 1 | 0 0 | 0 0 1 0 1 | 0 1 | 1 0 1 0 1 | 1 0 | 0 0 1 0 1 | 1 1 | 1 0 1 1 0 | 0 0 | 0 0 1 1 0 | 0 1 | 1 0 1 1 0 | 1 0 | 1 0 1 1 0 | 1 1 | 0 0 1 1 1 | 0 0 | 0 0 1 1 1 | 0 1 | 1 0 1 1 1 | 1 0 | 1 0 1 1 1 | 1 1 | 1 1 0 0 0 | 0 0 | 1 1 0 0 0 | 0 1 | 0 1 0 0 0 | 1 0 | 0 1 0 0 0 | 1 1 | 0 1 0 0 1 | 0 0 | 1 1 0 0 1 | 0 1 | 0 1 0 0 1 | 1 0 | 0 1 0 0 1 | 1 1 | 1 1 0 1 0 | 0 0 | 1 1 0 1 0 | 0 1 | 0 1 0 1 0 | 1 0 | 1 1 0 1 0 | 1 1 | 0 1 0 1 1 | 0 0 | 1 1 0 1 1 | 0 1 | 0 1 0 1 1 | 1 0 | 1 1 0 1 1 | 1 1 | 1 1 1 0 0 | 0 0 | 1 1 1 0 0 | 0 1 | 1 1 1 0 0 | 1 0 | 0 1 1 0 0 | 1 1 | 0 1 1 0 1 | 0 0 | 1 1 1 0 1 | 0 1 | 1 1 1 0 1 | 1 0 | 0 1 1 0 1 | 1 1 | 1 1 1 1 0 | 0 0 | 1 1 1 1 0 | 0 1 | 1 1 1 1 0 | 1 0 | 1 1 1 1 0 | 1 1 | 0 1 1 1 1 | 0 0 | 1 1 1 1 1 | 0 1 | 1 1 1 1 1 | 1 0 | 1 1 1 1 1 | 1 1 | 1
We also need one instance of 2x1 Multiplexer (aka MUX1), which we leave to the student as an easy exercise.
MUX2
,MUX1
.MUX3
function.MUX1
function
as an easy exercise.MUX1
and MUX2
functions,
it took 5 lines to define the MUX3
function!def MUX1(x0,x1,s1):
y1 = NOT(s1)
y2 = AND(x0, y1)
y3 = AND(x1, s1)
y = OR(y2, y3)
return y
def MUX3(x0,x1,x2,x3,x4,x5,x6,x7,s1,s2,s3):
g1_y = MUX2(x0,x1,x2,x3,s2,s3)
g2_y = MUX2(x4,x5,x6,x7,s2,s3)
g3_y = MUX1(g1_y,g2_y,s1)
y = g3_y
return y
MUX3
on a simple input.#MUX3(0,0,0,0,0,1,0,0, 1,0,1)
MUX3(x0=0, x1=0, x2=0, x3=0, x4=0, x5=1, x6=0, x7=0, s1=1, s2=0, s3=1)
1
MUX3
input and check the output.print("x0 x1 x2 x3 x4 x5 x6 x7 | s1 s2 s3 | y")
print(50*'-')
for x0,x1,x2,x3,x4,x5,x6,x7,s1,s2,s3 in product([0,1], repeat=11):
if random()<0.97:
continue
y = MUX3(x0,x1,x2,x3,x4,x5,x6,x7,s1,s2,s3)
print(f"{x0} {x1} {x2} {x3} {x4} {x5} {x6} {x7} | {s1} {s2} {s3} | {y}")
x0 x1 x2 x3 x4 x5 x6 x7 | s1 s2 s3 | y -------------------------------------------------- 0 0 0 0 0 0 0 1 | 0 1 1 | 0 0 0 0 0 0 1 1 0 | 1 1 1 | 0 0 0 0 1 0 0 0 0 | 1 0 1 | 0 0 0 0 1 0 0 0 1 | 0 1 0 | 0 0 0 0 1 0 1 0 0 | 1 0 1 | 1 0 0 0 1 0 1 1 1 | 0 1 0 | 0 0 0 1 0 0 0 0 0 | 0 0 0 | 0 0 0 1 0 0 1 0 1 | 0 1 0 | 1 0 0 1 0 0 1 0 1 | 1 0 0 | 0 0 0 1 0 0 1 0 1 | 1 0 1 | 1 0 0 1 0 0 1 1 1 | 0 1 0 | 1 0 1 0 0 0 0 0 0 | 1 1 0 | 0 0 1 0 0 0 1 1 1 | 0 0 0 | 0 0 1 0 1 0 0 1 0 | 0 0 1 | 1 0 1 0 1 0 1 1 1 | 0 0 1 | 1 0 1 0 1 1 0 0 0 | 0 1 0 | 0 0 1 0 1 1 0 1 0 | 0 1 0 | 0 0 1 1 0 0 0 0 0 | 1 1 1 | 0 0 1 1 0 0 1 0 1 | 1 0 0 | 0 0 1 1 0 0 1 1 0 | 0 0 1 | 1 0 1 1 0 1 0 0 1 | 1 1 1 | 1 0 1 1 1 0 1 1 1 | 1 1 1 | 1 0 1 1 1 1 0 1 1 | 1 1 1 | 1 1 0 0 0 0 1 1 0 | 1 1 1 | 0 1 0 0 0 1 1 0 0 | 1 0 0 | 1 1 0 0 0 1 1 1 0 | 0 0 1 | 0 1 0 0 1 0 0 0 0 | 1 1 0 | 0 1 0 0 1 0 1 0 1 | 0 0 0 | 1 1 0 0 1 0 1 1 1 | 1 0 1 | 1 1 0 0 1 1 0 0 0 | 0 1 1 | 1 1 0 0 1 1 0 0 1 | 1 1 1 | 1 1 0 0 1 1 0 1 0 | 0 0 0 | 1 1 0 0 1 1 0 1 1 | 1 1 0 | 1 1 0 0 1 1 1 0 0 | 1 0 1 | 1 1 0 1 0 0 0 0 0 | 1 0 0 | 0 1 0 1 0 0 1 1 0 | 1 0 1 | 1 1 0 1 0 1 0 0 0 | 1 0 0 | 1 1 0 1 0 1 1 0 0 | 0 1 1 | 0 1 0 1 0 1 1 0 1 | 1 0 1 | 1 1 0 1 1 0 0 1 0 | 1 1 0 | 1 1 0 1 1 1 0 1 0 | 0 1 1 | 1 1 0 1 1 1 0 1 0 | 1 1 1 | 0 1 0 1 1 1 1 0 1 | 1 0 1 | 1 1 0 1 1 1 1 1 0 | 0 0 1 | 0 1 1 0 0 0 0 1 0 | 0 0 0 | 1 1 1 0 0 0 1 0 1 | 1 0 1 | 1 1 1 0 0 0 1 0 1 | 1 1 1 | 1 1 1 0 0 0 1 1 1 | 1 0 0 | 0 1 1 0 0 1 0 0 0 | 0 1 0 | 0 1 1 0 0 1 0 0 0 | 1 1 0 | 0 1 1 0 0 1 1 1 0 | 0 1 0 | 0 1 1 0 0 1 1 1 0 | 1 0 1 | 1 1 1 0 1 0 0 0 0 | 1 1 0 | 0 1 1 0 1 0 1 1 1 | 0 0 0 | 1 1 1 0 1 1 0 0 1 | 1 1 0 | 0 1 1 0 1 1 1 0 1 | 1 0 1 | 1 1 1 1 0 0 0 0 1 | 1 0 1 | 0 1 1 1 1 0 1 1 1 | 0 1 0 | 1 1 1 1 1 1 0 0 1 | 0 0 1 | 1 1 1 1 1 1 1 1 1 | 1 0 0 | 1
Here is a simple design for 3 bits adder with carry in (cin) and carry out (cout) bits
cin
.def ADDER3(a2, a1, a0, b2, b1 ,b0, cin):
g1_y = XOR(cin, a0)
g2_y = XOR(b1, a1)
g3_y = XOR(b2, a2)
g4_y = MUX1(a0, b0, g1_y)
g5_y = MUX1(b1, g4_y, g2_y)
cout = MUX1(a2, g5_y, g3_y)
y2 = XOR(g5_y, g3_y)
y1 = XOR(g4_y, g2_y)
y0 = XOR(g1_y, b0)
return cout, y2, y1, y0
ADDER3(a2=0, a1=0, a0=1, b2=0, b1=0 ,b0=1, cin=0)
(0, 0, 1, 0)
This result should be of course interpreted as follows
cout=0, y2=0, y1=1, y=0
which is the correct result 10
.
ADDER3(a2=0, a1=1, a0=1, b2=0, b1=1 ,b0=1, cin=0)
(0, 1, 1, 0)
a<8:0> + b<8:0> + cin
Output: y<8:0> + cout
def ADDER9(a8, a7, a6, a5, a4, a3, a2, a1, a0, b8, b7, b6, b5, b4, b3, b2, b1, b0, cin):
g1_cout,y2,y1,y0 = ADDER3(a2, a1, a0, b2, b1, b0, cin)
g2_cout,y5,y4,y3 = ADDER3(a5, a4, a3, b5, b4, b3, g1_cout)
cout,y8,y7,y6 = ADDER3(a8, a7, a6, b8, b7, b6, g2_cout)
return cout, y8, y7, y6, y5, y4, y3, y2, y1, y0
ADDER9(a8=0, a7=0, a6=0, a5=0, a4=0, a3=0, a2=0, a1=0, a0=1, b8=0, b7=0, b6=0, b5=0, b4=1, b3=1, b2=1, b1=1, b0=1, cin=0)
(0, 0, 0, 0, 1, 0, 0, 0, 0, 0)