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)

Linker Module Example

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
AutoUpdater.add_link(CNTRL_V[:2], SLAVE0[1:-1])

# Connecting the last two bits of CNTRL_V to the middle two bits of SLAVE1
AutoUpdater.add_link(CNTRL_V[-2:], SLAVE1[1:-1])

# Impressing 5v and 0v on the SLAVE0 Bus
AutoUpdater.add_link(VCC[0], SLAVE0[0])
AutoUpdater.add_link(VCC[1], SLAVE0[-1])

# Impressing 5v and 0v on the SLAVE1 Bus
AutoUpdater.add_link(VCC[0], SLAVE1[0])
AutoUpdater.add_link(VCC[1], SLAVE1[-1])

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[0].set_voltage(5.5)

print (SLAVE0[0].get_voltage())

In [ ]:
print (SLAVE1[0].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_[0],
               2:A_[0],
               3:SEL[3],
               4:SEL[2],
               5:SEL[1],
               6:SEL[0],
               7:CAR,
               8:MOD,
               12:int(PWR[0]),
               18:B_[3],
               19:A_[3],
               20:B_[2],
               21:A_[2],
               22:B_[1],
               23:A_[1],
               24:int(PWR[1])
            })

alu.run()

In [ ]:
alu.draw_IC()

Verify with the function table of IC74181 ( Taken from google images )

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

Demux Block ( from google images )

Full Adder - Using Connectors in Combinational blocks


In [ ]:
print(FullAdder.__doc__)

In [ ]:
a, b, ci, s, co = Connector(1), Connector(0), Connector(1), Connector(), Connector()

# Initializing full adder using connectors
fa = FullAdder(a, b, ci)

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

Counters

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[0].voltage - c_t.outputs[1].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[1]):
    data[0][i] = m_t.last_updated_time + m_t.time_period * i
    data[1][i] = c_t.outputs[0].voltage
    time.sleep(c_t.sampling_time_interval)

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

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