- Link to GITHUB repository
Link to the the Google Colaboratory notebook

- The Google Colaboratory notebook has the advantage that you can run PyCircPL code from it without having to install PyCircPl or even Python on your local system.
- You first need to copy it to your google drive (or github repository)
- You can also dowload it to your local device and open it as a Jupyter notebook (if you have it installed with your Python).

- The
**PyCircPL**package can be installed on your local system by running the following command from the command line`pip install pycircpl`

- Or you may try running one of the following commands from this notebook.
- If you are running it from a Jupyter notebook on your local system, then it will be installed on your device.

In [ ]:

```
# 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
```

**After installation, you may need to restart this notebook.**

- After installing the
**PyCircPL**package, you need to import it. - The following command imports
**PyCircPL**to your Python interpreter.

In [ ]:

```
from pycircpl import *
```

**PyCircPL**is a simple Python package for simulating Logic Circuits specifically designed for educational use in introductory computation and digital logic college courses.- As such, it was primarily tuned for simplicity, readability,
convenience, and fast learning curve.
- Less for speed or industrial production.

- It is a lightweight package especially designed for small to medium scale circuits, such as those that are studied in introductory academic courses on the theory of computation and electronic digital design.
- Its main characteristic is that a digital circuit or a boolean formula can be easily defined by a series of simple Python commands, rather than an external static language.
- So, the only requirement is basic knowledge of the Python programming language, with a little programming skill.
- Experienced Python programmers can probably benefit a lot more from this package.
- It can be a useful companion for theoretical courses on
computation models and languages who wish also to engage
the students with some programming experience and skills.
- It is planned to be used in such a course by the author (Hebrew book at http://samyzaf.com/afl.pdf).
- It enables students to easily model and experiment with
- Typical logic circuit design
- Logic Circuit or Boolean formula Validation
- Logic Circuit Testing
- Logic problem solving

- It does provide an opportunity for students to develop and practice some programming skills while covering the theoretical computation course.
- In this tutorial, we will cover:
- Basic usage of PyCircPL for simulating Logic Circuits or Boolean formulas.
- A short survey of the commands and tools of the
**PyCircPL**package. - Advanced usage of PyCirc for experienced Python programmers (TODO).

We start with a very simple logic circuit which we call

**"FOO"**whose**PyCirc Diagram**is given below- Inputs gates: $x_1$, $x_2$, $x_3$
- Output gates: $y_1$, $y_2$
- Logic formula: $(y_1, y_2) = (x_1 \land x_2 , \ \neg x_3)$
See https://www.samyzaf.com/pycirc/pycirchdl.html for detailed information on PyCirc diagrams.

Here is the

**PyCircPL code**for modeling this circuit:

In [ ]:

```
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**!- So you need to
**run**its cell in order to execute it.

- So you need to
A circuit definition starts with the stabdard Python keyword

`def`

which is used to define a Python function.- The function name is identical to the circuit name
`FOO`

. - The function argument list is identical to the circuit boolean
input list
`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

In [ ]:

```
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.

In [ ]:

```
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}")
```

- After successful design of a circuit function such as
**FOO**, it can be placed in a single python code file and loaded as usual with the Python`import`

command.

- You may want to copy the function library that we use in this notebook to your local pc.
- Here is a link to the Python file that contains all functions that we use in this notebook:

- Here is an example of cell called
**FRED**which uses an instance of the cell**FOO**as one of its building blocks. - Note that this cell is using a gate of type
`FOO`

whose function we defined in Example 1 above.

- It is easy to write its corresponding function.
- Note that the function
`FOO`

is used in the function`FRED`

.

In [ ]:

```
def FRED(x1, x2):
g0 = 0
y1,y2 = FOO(x1,x2,g0)
return y1,y2
```

- The follwing cell
**HAM**contains two gates of type**FOO**and two gates of type**XOR3**. - The
**XOR3**cell is a typical**xor**cell with 3 input gates. It also contains one gate of type

**NOT**.

- Here is a
**PyCircPL code**for modeling this cell. - Notice lines 2 and 3 of the code that use the function
`FOO`

.

In [ ]:

```
# 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
```

- Here is a simple code for generating the truth table of
`HAM`

. - We use the
`product`

utility from the`itertools`

module (which is automatically loaded by pycircpl) to generate all the possible combinations of 4 boolean values.

In [ ]:

```
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}")
```

- The following circuit (given simple digraph represantation) counts the number of 1-bits of its input list.

- This is the correponging Python function:

In [ ]:

```
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
```

- Converting the graph diagram to the corresponding Python code is an easy task.
- Each non-input gate
`g1`

,`g3`

,`g2`

,`g4`

,`g5`

,`y1`

,`y2`

, is converted to its corresponding Python statement by simply applying its logical operator on its inputs. - Note that gate order is critical! You have to go from low to high depth.
- For example, gate variable
`g3`

must be defined befor`g2`

since it is an input to`g2`

!

- For example, here is what happens we apply
`COUNT3`

on the input list`x1 = 1 ; x2=0 ; x3 = 1`

In [ ]:

```
COUNT3(1,0,1)
```

Out[ ]:

(1, 0)

- The number of 1-bits in the input
`(1,0,1)`

is 2 which in binary form is`(1,0)`

as the above calculation shows. - It is easy to verify all possible outcomes of this circuit function with the following code:

In [ ]:

```
for x1,x2,x3 in product([0,1], repeat=3):
y1,y2 = COUNT3(x1,x2,x3)
print(f"COUNT3({x1},{x2},{x3}) = ({y1}, {y2})")
```

- Note that the same applies if we are given a boolean formula of this logical circuit $$ \begin{array}{rcl} y_1 &=& (x_1 \wedge x_2) \vee ((x_1 \oplus x_2) \wedge x_3) \\ y_2 &=& (x_1 \oplus x_2) \oplus x_3 \end{array} $$ instead of its digraph representation.
- In most cases, the digraph diagram is more intuitive, and easier to work with than the algebraic formula.

- Multiplxers are important circuit elements in electronic design.
Here is a simple

**PyCirc Diagram**for a 4x1 Multiplexer circuit (aka MUX2)

In [ ]:

```
# 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
```

In [ ]:

```
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}")
```

- Now we build a function for a 8x1 Multiplexer circuit (aka
**MUX3**) - Note that the 8x1 Multiplexer diagram is using
our
**MUX2**circuit as one of its building blocks (two instance of MUX2 are needed). We also need one instance of 2x1 Multiplexer (aka

**MUX1**), which we leave to the student as an easy exercise.

- As you can see from the diagram we now have 8 inputs bits:
x0, x1, x2, x3, x4, x5, x6, x7,

and one output bit: y. - We only need 3 logic gates: g1, g2, and g3.
- g1 and g2 are two instances of
`MUX2`

, - g3 is an instance of
`MUX1`

.

- g1 and g2 are two instances of
- Here is the code for creating a
**PyCircPL**`MUX3`

function.- We leave to the student to figure out the
`MUX1`

function as an easy exercise. - Using the already defined
`MUX1`

and`MUX2`

functions, it took 5 lines to define the`MUX3`

function!

- We leave to the student to figure out the

In [ ]:

```
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
```

- Here is a simple call of
`MUX3`

on a simple input.

In [ ]:

```
#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)
```

Out[ ]:

1

- In the next example we sample 3% of the truth assignments to the
`MUX3`

input and check the output.

In [ ]:

```
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}")
```

Here is a simple design for 3 bits adder with carry in (cin) and carry out (cout) bits

- This circuit acceps three types of input
- two binary numbers: $(a_2,a_1,a_0)$, $(b_2,b_1,b_0)$
- a carry in bit:
`cin`

.

- Its output $(y_2,y_1,y_0)$ is the binary sum of the two numbers (with the carry added).
- In case of addition overflow, we need a carry out (cout) output bit as well.
- The following
**PyCircPL**code describes a model for the**ADDER3**cell.

In [ ]:

```
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
```

- For example, lets try to compute 001+001

In [ ]:

```
ADDER3(a2=0, a1=0, a0=1, b2=0, b1=0 ,b0=1, cin=0)
```

Out[ ]:

(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`

.

In [ ]:

```
ADDER3(a2=0, a1=1, a0=1, b2=0, b1=1 ,b0=1, cin=0)
```

Out[ ]:

(0, 1, 1, 0)

- Here is a simple
**PyCirc Design Diagram**for the standard 9-bits adder with a carry in (cin) and carry out (cout) bits. - It uses 3 gates g1, g2, g3, of type
**ADDER3**which are chained by their cout/cin pins. **Input:**`a<8:0> + b<8:0> + cin`

**Output:**`y<8:0> + cout`

- Here is a
**PyCircPL**code for**ADDER9**:

In [ ]:

```
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
```

- Lets test this function withe the following input

In [ ]:

```
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)
```

Out[ ]:

(0, 0, 0, 0, 1, 0, 0, 0, 0, 0)

- For the more experienced students we present here some more advanced features of the PyCircPL package will be added within the near future. To be contniued ...