``````

In [ ]:

# Imports
from BinPy import *

``````

### Logic Gates Example - 2 Input / Multi Input Logic Gates

BinPy has basic logic gates like AND, OR, XOR, XNOR, NAND and NOR which accepts both numeric inputs as well as connector inputs.

``````

In [ ]:

# XOR GATE in BinPy

# Create a gate instance
xor1 = XOR(1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1)

xor1.output()

``````
``````

In [ ]:

# Using a Connector in BinPy

A = Connector(1) # input 1
B = Connector(0) # input 2
C = Connector()  # output

xor1.set_inputs(A, B)  # Overrides previous inputs
xor1.set_output(C)

``````
``````

In [ ]:

C.get_logic() # or use C.state

``````
``````

In [ ]:

A.set_logic(0);
C.get_logic()

``````

### Usage of Bus object in BinPy

``````

In [ ]:

# Using Bus in BinPy

``````
``````

In [ ]:

# Initiating the Bus with connectors

bit_1 = Connector(0)
bit_2 = Connector(1)
bit_3 = Connector(0)
bit_4 = Connector(0)

bus_a = Bus(bit_1, bit_2, bit_3, bit_4)

``````
``````

In [ ]:

# Probing the logic of the Bus

bus_a.get_logic_all(as_list=False)

``````
``````

In [ ]:

# Probing the voltage of the Bus -- The logic states can be represented by a 5v logic
# To get another logic family edit the binpy.config file to set the desired logic HIGH and logic LOW parameters

bus_a.get_voltage_all()

``````
``````

In [ ]:

# Creating another Bus from an existing Bus

bus_b = Bus(bus_a)

bus_b.get_voltage_all()

``````
``````

In [ ]:

# Concatenating the two Bus-es

bus_c = bus_a + bus_b

bus_c

``````
``````

In [ ]:

# Copying values between buses

bus_d = Bus(8) # 8 indicates the length of the Bus. By default all busses will be digital in type

print (bus_d)

``````
``````

In [ ]:

bus_d.copy_values_from(bus_c)
print (bus_d)

``````
``````

In [ ]:

# Iterating through a bus and setting names ( TAG's ) for connectors
i = 7
for connector in bus_d:
connector.set_name("B"+str(i))
print ( connector )
i -= 1

``````
``````

In [ ]:

# Probing the connector tags of each bus

print (" ".join( [ connector.name for connector in bus_d ]))

``````
``````

In [ ]:

# Analog Bus

PWR = Bus(Connector(voltage=5.2), Connector(voltage=0))

print (PWR)

``````
``````

In [ ]:

# Bus is an abstraction over the python lists, so you can do all list operations on it

# Slicing Bus-es

bus_e = Bus(bus_d[:-4])

print (bus_e == bus_b)

``````
``````

In [ ]:

# Circulary rotating the bits of the Bus-es

print ("Before rotation, bus_e : ", bus_e)

print ("After circularly rotating right by 3 positions, bits of bus_e: ", bus_e >> 3)

# Note that this returns a list of the Connectors and not a bus object. Use Bus(bus_e>>2) to return a Bus object

print ("After circularly rotating left by 3 positions, bits of bus_e: ", bus_e << 3)

``````

BinPy's linker modules stores all the connection in a NetworkX Graph data structure and traverses trought it iteratively in the background to reflect changes to the outputs / inputs of the ports.

Simulating the below connection ( drawn using KiCAD ) using BinPy's Linker module.

``````

In [ ]:

# Using the linker module to connect two Bus-es
CNTRL_V = Bus(4)

CNTRL_V.set_voltage_all(5.0,5.0,2.0,2.5)

VCC = Bus(Connector(voltage = 5.2), Connector(voltage=0))

``````
``````

In [ ]:

SLAVE0, SLAVE1 = Bus(4), Bus(4)

SLAVE0.set_type(analog = True)
SLAVE1.set_type(analog = True)

# Connecting the first two bits of CNTRL_V to the middle two bits of SLAVE0

# Connecting the last two bits of CNTRL_V to the middle two bits of SLAVE1

# Impressing 5v and 0v on the SLAVE0 Bus

# Impressing 5v and 0v on the SLAVE1 Bus

``````
``````

In [ ]:

print (SLAVE0.get_voltage_all())

``````
``````

In [ ]:

print (SLAVE1.get_voltage_all())

``````
``````

In [ ]:

CNTRL_V.set_voltage_all(5.0, 6.0, 2.0, 1.1)

``````
``````

In [ ]:

# The Slave Bus-es have been updated with the updated voltage in CNTRL_V

print (SLAVE0.get_voltage_all())

``````
``````

In [ ]:

print (SLAVE1.get_voltage_all())

``````
``````

In [ ]:

# Unlinking the SLAVE0 from CNTRL_V

AutoUpdater.remove_link(SLAVE0[1:-1]) # Only the middle 2 ports are connected to CNTRL_V
# VCC is still connected to SLAVE0

``````
``````

In [ ]:

CNTRL_V.set_voltage_all(3.0, 2, 1, 6.2)

``````
``````

In [ ]:

# SLAVE0 Retains the last held value

print (SLAVE0.get_voltage_all())

``````
``````

In [ ]:

# SLAVE1 however is updated

print (SLAVE1.get_voltage_all())

``````
``````

In [ ]:

# Change in VCC is reflected to both SLAVE0 and SLAVE1

VCC.set_voltage(5.5)

print (SLAVE0.get_voltage())

``````
``````

In [ ]:

print (SLAVE1.get_voltage())

``````

### Example to illustrate the usage of bittools

`BinPyBits` is a class inheriting from the `bitstring.BitArray` class. It will be used for efficient manipulation / handling of Bit vectors.

``````

In [ ]:

from BinPy import *

# Initializing a BinPyBits object

bit_vector = BinPyBits(5)

``````
``````

In [ ]:

# By default all BinPyBits objects are not signed

bit_vector.signed

``````
``````

In [ ]:

# Getting the decimal value

bit_vector.uint

``````
``````

In [ ]:

# Getting the binary string

bit_vector.bin

``````
``````

In [ ]:

# Do not use int with unsigned BinPyBits objects

bit_vector.int

``````
``````

In [ ]:

# This returns -3 since '101' ==> -3 ( 2's Complement representation )

# You could use :

int_value = bit_vector.int if bit_vector.signed else bit_vector.uint

print int_value

``````
``````

In [ ]:

# Creating a BinPyBits object using binary string

bit_vector = BinPyBits('1111', signed=False)

# Converting to decimal

int_value = bit_vector.int if bit_vector.signed else bit_vector.uint

print int_value

``````
``````

In [ ]:

# Creating a signed BinPyBits

bit_vector = BinPyBits('1111', signed=True)

# Converting to decimal

int_value = bit_vector.int if bit_vector.signed else bit_vector.uint

print int_value

``````
``````

In [ ]:

# Converting to hex

bit_vector.hex

``````

Refer the documentation of bittstring to discover additional functionality.

``````

In [ ]:

# The speciality of BinPyBits lies in the fact that it can be initialized from various types of inputs
# Except for the initialization, the rest of the functionalities remain similar to that of the bitstring.BitArray

# Initializing a signed value using - sign

bit_vector = BinPyBits('-1111', signed=True)

print bit_vector.int

``````

### Multiplication algorithms in BinPy

#### Roberson's Multiplication algorithm

``````

In [ ]:

product = robertsons_multiply('111111', '111111', 7)
print product

``````
``````

In [ ]:

to_unsigned_int(product)

``````
``````

In [ ]:

product = robertsons_multiply('111111', '000001', 7, signed=True)
print product

``````
``````

In [ ]:

to_signed_int(product)

``````

#### Booth's Multiplication alogrithm

``````

In [ ]:

product = booths_multiply('110011', '111001')
print product

``````
``````

In [ ]:

to_unsigned_int(product)

``````
``````

In [ ]:

product = booths_multiply(bin(-5), bin(-5), signed=True)
print product

``````
``````

In [ ]:

to_signed_int(product)

``````

#### Karatsuba's Multiplication algorithm

``````

In [ ]:

product = karatsuba_multiply('1010', '1001')
print product

``````
``````

In [ ]:

print  to_unsigned_int(product)

``````
``````

In [ ]:

product = karatsuba_multiply('1010', '1001', signed=True) # --6  *  -7  = 42
print product

``````
``````

In [ ]:

print to_unsigned_int(product)

``````

### IC's - Using Integrated Circuits in BinPy

``````

In [ ]:

# Usage of IC 7400:

ic1 = IC_7400()

print(ic.__doc__)

``````
``````

In [ ]:

ic1.draw_IC()

``````
``````

In [ ]:

pin_dict = {1: 1, 2: 0, 4: 0, 5: 0, 7: 0, 9: 1, 10: 1, 12: 0, 13: 0, 14: 1}
ic1.set_IC(pin_dict)
ic1.run()

``````
``````

In [ ]:

ic1.draw_IC()

``````
``````

In [ ]:

alu = IC_74181()

``````
``````

In [ ]:

print alu.__doc__

``````
``````

In [ ]:

alu.draw_IC()

``````
``````

In [ ]:

# Complementing A Bus and B Bus
#_ = [ ( NOT(A[i]).set_output(A_Compl[i]), NOT(B[i]).set_output(B_Compl[i]) ) for i in range(4) ]

``````
``````

In [ ]:

# Input Bus A and B
A, B = Bus(4), Bus(4)

# Power supply of VCC 5v / GND 0v
PWR = Bus(Connector(voltage=0), Connector(voltage=5.0))

MOD = 1
SEL = list(reversed([1, 1, 1, 0]))
CAR = 1

``````
``````

In [ ]:

A.set_logic_all('1000') # 8
B.set_logic_all('0001') # 1

A_ = A.get_logic_all() # I might as well store A_ directly as a list of logic bits ...
B_ = B.get_logic_all()

alu.set_IC({   1:B_,
2:A_,
3:SEL,
4:SEL,
5:SEL,
6:SEL,
7:CAR,
8:MOD,
12:int(PWR),
18:B_,
19:A_,
20:B_,
21:A_,
22:B_,
23:A_,
24:int(PWR)
})

alu.run()

``````
``````

In [ ]:

alu.draw_IC()

``````

### Combinational circuits

#### DEMUX

``````

In [ ]:

# Initializing the DEMUX class

# Must be a single input

demux = DEMUX(1)

# Put select lines

# Select Lines must be power of 2

demux.select_lines(0, 1) # a, b

# Output of demux

print (demux.output())  # D0, D1, D2, D3

``````

### Full Adder - Using Connectors in Combinational blocks

``````

In [ ]:

``````
``````

In [ ]:

a, b, ci, s, co = Connector(1), Connector(0), Connector(1), Connector(), Connector()

# Initializing full adder using connectors

# Connect outputs
fa.set_output(0, co)
fa.set_output(1, s)

``````
``````

In [ ]:

print (co.get_logic(), s.get_logic())

``````

### MUX

``````

In [ ]:

# Initializing the MUX class

mux = MUX(0, 1, 1, 0)

# Put select lines

mux.select_lines(0, 0)

# Output of mux

print (mux.output())

``````

### Sequential Circuits - Introducing the clock object.

#### JK Flip Flop and the The ASCII Art based Oscilloscope of BinPy

``````

In [ ]:

j, k, p, q = Connector(1), Connector(0), Connector(0), Connector(1)

# A clock of 4 hertz frequency initialized to 1
clock = Clock(0, 4)

jkff = JKFlipFlop(j, k, Connector(1), clock.A, clear=Connector(1))

# To connect outputs use s.set_outputs(op1,op2)
jkff.set_outputs(A=p, B=q)

# Initiating the oscilloscope
o = Oscilloscope((clock.A, 'CLK'), (j, 'J'), (
k, 'k'), (p, 'OUT'), (q, 'OUT!'), (Connector(1), 'ENABLE'))

o.start()
o.set_scale(0.02)  # Set scale by trial and error.
o.set_width(100)
o.unhold()

# For each j,k in 0,0 / 0,1 / 1,0 / 1,1 Trigger ( Send a Falling edge clock pulse to the jkff module )

from time import sleep

for j.state, k.state in zip([0, 0, 1, 0, 1], [0, 0, 0, 1, 1]):

print "j,k = ", ( j.state, k.state )
# Sending One Clock Pulse

# The same thing can also be done by --> jkff.setInputs(j = 1, k = 0)
while True:
if clock.A.state == 0:
# Falling edge will trigger the FF
jkff.trigger()
break
print (jkff.state())

# Sending a positive edge to jkff
while True:
if clock.A.state == 1:
# Falling edge will trigger the FF
jkff.trigger()
break

o.display()

``````

### Multivibrator

``````

In [ ]:

# MODE selects the mode of operation of the multivibrator.

# Mode No. :  Description
#   1          Monostable
#   2          Astable
#   3          Bistable

out = Connector()

``````

#### Multivibrator in mode 1 - Monostable mode

``````

In [ ]:

# Initialize mutivibrator in MODE 1

m = Multivibrator(0, mode=1, time_period=1)
m.set_output(out)

# Initialize the oscilloscope
o = Oscilloscope((out, 'OUT'))
o.set_scale(0.005)  # Set scale by trial and error.
o.set_width(100)
o.unhold()
time.sleep(0.1)
m.trigger()  # Also works with m()
time.sleep(0.1)

# Display the oscilloscope
o.display()
m.kill()
o.kill()

``````

### Analog Buffer

``````

In [ ]:

# Source Bus
a = Bus(4)
a.set_type(analog=True)
a.set_voltage_all(3.5, 6.7, 2.2, 1.1)

# Ouput Bus
b = Bus(4)
b.set_type(analog=True)

# Enable input
e = Bus(1)
e.set_logic_all(1)

``````
``````

In [ ]:

# Initializing an analog Buffer
buff1 = AnalogBuffer(a, b, e, 0.8) # With an attenuation of 0.8, relay the input to the output

``````
``````

In [ ]:

print b.get_voltage_all()

``````
``````

In [ ]:

# BinPy automatically converts the voltage to logic state based on 5v-0v logic
print b.get_logic_all()

``````
``````

In [ ]:

print b.get_voltage_all()

``````
``````

In [ ]:

# Changing the input

a.set_voltage_all(1,1,1,1)

``````
``````

In [ ]:

b.get_voltage_all()

``````
``````

In [ ]:

# Changing the attenuation level

buff1.set_attenuation(0)

``````
``````

In [ ]:

b.get_voltage_all()

``````

### Analog Signal Generator - Amplitude Modulation using 2 Analog Signal Generators

``````

In [ ]:

# AM Signal generation using 2 intances of Signal Generator modules.

import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
import math, time

# Message sine signal generator of frequency 10 Hz and 1 Vpp amplitude
m_t = SignalGenerator(typ = 0, freq = 10, ampl = 1)
m_t.set_offset(-0.5)

# Carrier sine signal generator of frequency 100 Hz and 10 Vpp amplitude
c_t = SignalGenerator(typ = 0, freq = 100, ampl = 10)
c_t.set_offset(-5) # To make the range as [-5, 5]
c_t.set_modulation_input(m_t.outputs)
c_t.set_modulation_type(1)

time.sleep(0.5) # To allow setup time

c_t.last_updated_time, (c_t.outputs.voltage - c_t.outputs.voltage)

# Populate the plot points to data array

data = np.zeros(shape = (2, math.ceil(m_t.time_period / c_t.sampling_time_interval)))

for i in range(data.shape):
data[i] = m_t.last_updated_time + m_t.time_period * i
data[i] = c_t.outputs.voltage
time.sleep(c_t.sampling_time_interval)

# Plot the modulated signal for the given timeframe
fig, ax = plt.subplots()
ax.plot(data, data)
plt.show()

# Kill the signal generator threads after use
m_t.kill()
c_t.kill()

``````