Python Advanced features: Whatever is mentioned below works for python 3.6 and later (unless mentioned specifically)

Modules:

We saw functions in the previous section. However, since these built in functions are limited, python provides modules which have more functions defined in them. These modules can be imported in other python pgm, so that we don't have to write our own functions for commonly used tasks. There are modules written for almost everything in python, so search python modules before you start writing your function for something complicated. We can create our own modules with our functions in them.

when we want to implement any functionality in module, we can write them as functions. However, if we are working in OOP, we can write them as methods. Most modules provide both function and method support for a given functionality, so you can use any one depending on your preference.

module: are python files (*.py) consisting python code. Any python file can be referenced as module (there's nothing special about a module). Python file called "hello.py" has module name of "hello" that can be imported into other python files. Modules can define functions, classes and variables, and can have any additional code outside of func/classes (i.e calling these functions, running test cases, etc). All of the code within file file1.py is part of the module "file1".

Packages:

We can bundle multiple modules (or multiple files) together to form a package. A package is basically a directory with Python files and a file with the name __init__.py (init with 2 underscore _ on both sides of init). This means that every directory inside of the Python path, which contains a file named __init__.py, will be treated as a package by Python. It's possible to put several modules into a Package. Package is just for convenience where several modules are bundles into one: modules in a package behave exactly like any other module. Package is treated like a higher level module.

ex: we create dir pkg1, and put several files file1.py, file2.py etc in it. Additionally, we need to put init file "__init__.py:, which can be an empty file (but is needed to identify dir pkg1 as a package and not just a dir).  Now the whole dir is treated as package "pkg1". Now we can use dotted module names, i.e A.B is used to refer to module B in package A

Import modules in python:

Once we have downloaded the modules, we can call it in any other python pgm. To do that, first we have to import that module using "import" cmd.

ex: import hello => imports hello module (hello.py has module name of hello, so here hello.py is being included in our current pgm). Any print stmt etc or any function called inside hello.py will be executed, as importing module or file is same as doing an "include file" or putting the code of that imported file in our current file at that location, where import is being called.

ex:
name1.py: python script that contains func "print_def".
main.py: python script thats calls func from name1.py

Import from any dir:

So contents of main.py will look like this:
import name1 => import entire module from name1.py. It first searches for builtin module named "name1", and on not finding a built in module, searches for file named "name1.py" in current dir and then dir specified in "sys.path" var or in environment variable "PYTHONPATH" (from your operating system). By default, "sys.path" includes python system dir only, but we can add user specified dir in it too. If name1 module is in subdir, then we can specify the relative path here as "import subdir1.dir2.name1". We can go up the dir structure by using 1 dot to go one dir above, but it doesn't seem to work for me (i.e import ...dir1/file3 should take you 3 levels higher to dir1, but it gives an error as invalid syntax).

ex: name1.print_def("zara"); => now any func, etc can be called from that module, by specifying name of the module (i.e file), followed by a dot and then the name of func/method/attribute. This is assuming name1 module is in current dir.

ex: subdir1.dir2.name1.print_def("zara"); => This is needed if we imported module from some subdir in current dir (i.e "import subdir1.dir2.name1").

To import modules in any dir (not just subdir starting from current dir), we can't to do in above way. We need to add dir path to sys.path

sys.path.append('/home/user1/dir1/') => Now this dir is added to python sys.path var, so that python will look in this dir also, when searching for modules

sys.path.append('../../dir1/') => Relative paths (relative to current dir where script is running) can also be provided

One very easy way to include modules in any dr is to make soft links to other dir containing these modules in current dir (i.e ln -s ../../dir1 .). Now, all modules are available in subdir of current dir, which can now easily be accessed by doing "import dir1.dir2.name1".

NOTE: many modules are automatically included with python distribution, so you do not need to downlaod them, you call just import them, i,e import math => this imports math module w/o downloading math (as math module is already included with python)

alias: we can also modify the name of module,

ex: import math as m => here math is renamed as "m". So, now we can call math as m. We can also use package.module name i.e import mathplotlib.pyplot as plt. As a convention most modules are imported with a "given" alias name.

print (m.pi); => instead of using math.pi, we used m.pi (as we used alias above)

import specific function: Instead of importing the whole module which imports everything from that module, we can also import specific function from that module:

from random import randint => This is another style of importing func. Here func "randint" is moved from "random" namespace to current namespace, so that dot is no longer required. So, now randint can be called directly, i.e instead of random.randint(), we can call randint(). If we try to call random.randint() now, we'll get an error as "random" namespace is not present anymore. Note: only randint func is imported here (and any other func inside random module is not imported)

from random import * => This imports all functions from module random. Most of the times, we just use * as it's convenient, however if there are too many functions inside that module, then it may affect performance, as a lot of memory will be used up loading unneeded functions.

Import packages in python:

Importing packages is done in same way as importing modules. NOTE: pkg name is no different than dir name, so we can use usual "import from subdir" style that we used for modules above (i.e init files are not needed). Looks like for python3.6 and above, init files are not needed at all, as all ex below work without init file too.

ex: import pkg1 => Here pkg1 is imported, but not the modules inside it. So, if we try to access any module file1 or file2 (i.e pkg1.file1.func1() or pkg1/file2, etc), we'll get an error "name file1 not defined". We have to import modules explicitly.

ex: import pkg1.file1 => Here we import each module explicitly. This is no diff than importing file1.py from dir pkg1 as we saw above for importing modules for subdir. init file is not needed in this case. Here pkg1 namespace is still maintained for these func.  This is helpful in cases where there are functions with same names in different modules

pkg1.file1.func1() => Here full path is needed as file1 is still in namespace "pkg1"

We can also use the "from" style to import modules.Here "from" may have name of module or name of pkg.

ex: from pkg1 import file1, file2 => This is the most common way. This imports both file1.py and file2.py modules from package "pkg1" into current pgm. "from pkg1 import *" doesn't work (errors out), as * is for func/method within modules and not for modules themselves ?? Not sure??

file1.func1() => This function inside file1 module can now be called.

ex: from pkg1.file1 import * => This is valid syntax as we are importing all func from module file1 in pkg1.

 

pip:

To install a module which is not present on your python installation, simply use pip. The most common usage of pip is to install from the Python Package Index using a requirement specifier. Generally speaking, a requirement specifier is composed of a project name followed by an optional version specifier. There are many ways to install various modules in python, but pip is the preferred installer program. Starting with Python 3.4 for Python3 and Python 2.7.9 for Python2, it is included by default with the Python binary installers. So, always use pip to install modules.

To check if pip is installed, run cmd:

pip --version => Since we didn't specify python version here, this cmd runs with python version installed and pointing to python, i.e "python -m pip --version".

pip 18.1 from /usr/lib/python3.4/site-packages/pip (python 3.4) =>  On CentOS, this is the default o/p. This shows that pip is only installed for python3.4, and not for python2 or python3.6. If we run "python -m pip --version" or "python2 -m pip --version", it says "no module named pip" since python2 doesn't have pip installed (since I've python 2.7.5 installed by default). However, "python3.4 -m pip --version", shows pip installed for 3.4. Why "pip --version" shows pip for python3.4 is still puzzling, since this cmd invokes python2, which doesn't have pip installed. My guess is that cmd "pip --version" runs pip with all versions of python, and whichever is the earliest python version it finds pip for, it shows that one.
 
pip 9.0.1 from /usr/lib/python2.7/dist-packages (python 2.7) => On LinuxMint, this is the default o/p. This shows that pip is only installed for python2.7, and not for python3 or python3.6
 
So, above o/p shows that just having pip installed doesn't mean it will work with all python versions. For CentOS 7, pip is installed for python3.4, For Linux Mint, pip is installed for python2, but not for any version of pyhton3. You have to note which python version is pip installed for, and will work with only that python version. So, if we want to install a module for python3.6 using pip, it won't work if pip is not installed for python3.6. We are using python3.6, since it's the latest python version supported on both CentOS and Linux Mint (as of July, 2020).
 
NOTE: pip cmd may point to python2.x or python3.x depending on how it was installed. For that reason, we prefer to use pip3 cmd, which specifically refers o python3.x version
 
CentOS 7:
 
For CentOS 7, pip is installed for python3.4 by default. To install it for python 3.6, run below 2 cmds:
 
$ sudo yum install python3-devel => installs python development libraries (in centOS it's python-devel and NOT python-dev). python-devel is for python2, while python3-devel is for python3. python3 on my machine points to python3.6, it installs things for python3.6. There's no python3.6-devel or python36-devel, you either have it for python or python3. python-dev consists of "Header files, a static library and development tools for building Python modules, This needs to be installed for python3 before we can install pip for python3.
 
$ sudo yum install python3-pip => installs pip for python3. Again, this installs it for python3.6. There's no python3.6-pip or python36-pip.
 
$ python3.6 -m pip --version => just typing "pip --version" will shows pip installed for python3.4 (for reasons mentioned above). "python3 -m pip --version" also gives same below o/p as python3 points to python3.6.
pip 9.0.3 from /usr/lib/python3.6/site-packages (python 3.6) => This shows that pip got installed correctly for python3.6. Above in cmd "pip --version", this was pointing to python3.4
 
Linux Mint:
 
Since python3.6 is the only python3 installed, following cmds install pip for python3. FIXME: Looks like pip gets installed for all versions of python3? or only for version that python3 is pointing to?? FIXME
 
sudo apt install python3-distutils => this needed else we get couple of syntax errors when running next cmd

sudo apt install python3-pip => This installs pip for python3. if we use "sudo apt install python-pip", then it installs pip for python2.

python3 -m pip => Now running this cmd will either show options which means pip is installed correctly for python3, or it will say "module pip not found" which means pip didn't get installed correctly for python3. Or we can also type "python3 -m pip --version" to see version number of pip.

Install modules using pip:

Once pip is installed, we can use it to install other Python modules. cmds are same on both CentOS and LinuxMint.

ex: python3.6 -m pip install SomePackage => This installs package named "SomePackage". Here, instead of running pip directly, we prepend it with "python3.6 -m pip". This runs appropriate copy of pip for python3.6. This is helpful when there are multiple python versions installed on your system. Here pip is called for python3.6, so that SomePackage is installed for python3.6. This is preferred way, since there is no ambiguity on which version of python was this package installed for.

If module gets installed for python3, we see all those modules in this dir: /usr/local/lib64/python3.6/site-packages/*

However, if it's gets installed for python2, we see those modules here: /usr/lib64/python2.7/site-packages/*

We can use unix "locate" cmd to find out where these libs are. i.e locate python2 => shows all libs and other dir for python2.

Below are some very helpful modules that you should install as they may be required for many projects.

1.pygame: pygame is very popular gui package for python for building games. See on pygame.org for more details. It's very easy for kids to get started on this and build sleek games. To install pygame module on python3, run this:

$ pip install pygame => This installs pygame module on your system, so that it can be imported in any python file. However, here since we didn't provide specific python version, it uses copy of pip for python. This is equiv to "python -m pip install pygame". However, python here may be pointing to python2, python3 or python3.6, This may install pygame only for python version pointed to (if python points to python2, then pygame is only installed for python2. If we import pygame in python3 pgm, it will error out saying module not found), so better to specify python version

$ sudo python3 -m pip install -U pygame => this install pygame for python3. -U option updates pygame module if already installed. Here pygame package is installed for python3 (or python3.6 as python3 is pointing to python3.6). Or, we can run this cmd "python3 .6-m pip install pygame", it's the same thing. 

 

Important modules: Below are few modules that are already included with distribution, and are useful for carrying out many tasks.

Module random: It generates random numbers

import random => importing built in module named random. All methods or functions in random module are imported here

print (random.randint(1,30)) => calls func returning random int b/w range 1 to 30. Functions within modules are called by separating them via dot (to indicate that the function is in the namespace "random".

from random import randint => If we import randint function this way, then we just do print (randint(1,30)).

Module os: One  of the important modules is "os" module. It provides operating system i/f. exec* functions loads new pgm with optional args.

from os import execvp, envron => imports these 2 methods from os module. execvp loads new pgm, while environ function provides shells environment var.

exexvp(file, args) => this executes a new pgm specified by "file" (with args as specified), replacing the current process, they do not return. The new executable is loaded into current process.

environ['HOME'] => This provides the value of HOME environment var found in bash, csh, etc.

ex: os.rmdir(/home/scratch) => runs unix cmd rmdir, so causes /home/scratch dir to be removed

Module sys: This module provides  "argv" func to get access to cmd line args. sys.argv is list of cmd line args. We can also access individual args as sys.argv[0], sys.argv[1] etc

ex: file fil1.py

from sys import argv # or just "import sys"

print ("Args are", argv[0], argv[1], ...)

run file1.py abc 179 my_name => here argv[0]= file.py, argv[1]=abc, ...

Standard library: python's std lib contains many built in modules (written in C) that provides access to system functionality such as file I/O (which would otherwise be inaccessible to python programmers) as well as many python modules for a variety of simple/complicated tasks. This std lib contains data types, built in functions (so that no import is needed), and tons of modules. This std lib is included in any python pgm by default.

A. Date and Time:
import time;
ticks = time.time() => ticks is floating point num. 1st time is time module, while 2nd time is time method working on time module
print "Ticks=",ticks => prints ticks since Jan 1, 1970
localtime = time.localtime(time.time()) => gets local time

import calendar
cal = calendar.month(2008, 1) => gets calender for Jan 2008. Here calender is module and month is the method

regular expression:  helps match or find patterns using special syntax. This is very useful feature as most of the times scripts are used for matching, replacing, etc words in documents. module re along with it's many functions used for this.

1. module re: module re provides full support for Perl-like regular expressions in Python. The re module raises the exception re.error if an error occurs while compiling or using a regular expression.

match func:
re.match => re.match function returns a match object on success, None on failure.
We use group(num) (returns entire match or specific match num if specified) or groups() (returns all matching subgroups in tuple) function of match object to get matched expression.
syntax: re.match(pattern, string, flags=0)
Since various characters have special meaning when they are used in regular expression, to avoid any confusion while dealing with regular expressions, we would use Raw Strings as r'expression'.
control char: decides how to match
. => Matches any single character except newline. Using m option allows it to match newline as well.
[...] => Matches any single character in brackets. [abc] matches any of a or b or c
[^...] => Matches any single character not in brackets. ex: [^0-9] => match anything other than digit
re* => Matches 0 or more occurrences of preceding expression.
re+ => Matches 1 or more occurrence of preceding expression.
re? => Matches 0 or 1 occurrence of preceding expression.
a|b => Matches either a or b.
(re) => Groups regular expressions and remembers matched text. (.*) me => matches 0 or more char until "space me" matches
\d+ => match any num of digits \D => match any nondigit, \d{3,5} => match 3,4 or 5 digits
\s => match whitespace, \S => match non whitespace
\w => match single word char

ex:
<.*> => matches in greedy way. So will match full <python>perl> in <python>perl>
<.*?> => matches in non greedy way (since ? means 0 or 1 occurrence only). So will match <python> in <python>perl>
\bpyth(on|off)\b => match puthon or pythoff
ex:
import re => import re module
line = "Cats are smarter than dogs"
matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I) => (.*) matches char in greedy fashion (matches smallest possible), while (.*?) matches char in non-greedy fashion, and stored in group. re.M, re.I are optional flags. re.I performs case insensitive matching, while re.M makes $ match the end of a line (not just the end of the string) and makes ^ match the start of any line (not just the start of the string). multiple flas are provided by using exclusive OR (|)
if matchObj: => match obj returned on match, else None returned
   print "matchObj.group() : ", matchObj.group() => returns entire match since no num specified => Cats are smarter than dogs
   print "matchObj.group(1) : ", matchObj.group(1) => returns 1st match within () => Cats
   print "matchObj.group(2) : ", matchObj.group(2) => returns 2nd match within () => smarter. using (.*) will match in non greedy fashion, so it will match as much as possible until it finds spce and few char after it. so (.*) will match "smartar than" as there space after "than".
else:
   print "No match!!"

search func: similar to match in perl, where it looks for match anywhere in string (as opposed to match which looks for match only in beginning of string)
ex:
matchObj = re.match( r'dogs', line, re.M|re.I) => returns None since dogs is not at beginning of line
searchObj = re.search( r'dogs', line, re.M|re.I) => returns dogs since it appears somewhere in line

search and replace func:

ex:
phone = "2004-959-559 # This is Phone Number"
num = re.sub(r'#.*$', "", phone) => substitute anything after # until end of string with blank, so num = 2004-959-559
num = re.sub(r'\D', "", phone) => substitute any non digit with blank, so num =  2004959559. we don't need \D+ as sub does global replacement on that string for every occurnce of that pattern.

CGI, database access, webpage, sending email all can be achieved via python by importing particular modules

 

Static Timing Analysis (STA) Flow:

Once we have converted RTL to netlist, it's time to run Timing analysis on gate level netlist. When we have RTL, there is no concept of gates or delay. RTL is modeled as zero delay, so there is no delay, and as such no setup or hold times to meet. However, once this RTL is converted to gates, we have real delays for these gates. In order for this netlist to behave the same as RTL, all paths need to meet timing.

Static Timing Analysis commonly known as STA is the flow where we run a timing tool that analyzes all paths and finds out f there is any setup/hold violation on any of these paths.

Tools:

There are STA software available from both cadence and synopsys as well in open source world. PrimeTime (PT) from Synopsys is considered the gold standard for STA. Cadence has it's own tool called Encounter Timing System aka ETS. These are the 2 timing tools that are used by almost all the companies to run STA. From open source side, we have Vesta written by Tim Edwards. There is also another open source STA called OpenTimer. We'll discuss about each of these tools separately in their own sections.

STA timing corners:

STA requires running design at various corners. Recall that a tarnsistor's delay depends on PVT (see in solid state devices section for more details).

We run timing on max, min and typ corners. We run both "setup" and "hold" runs across all 3 corners (typ corner is optional, and many times just running max and min corner suffices)

 

 

 

 

Raspberry 4:

This is the latest and most powerful raspberry pi, released in 2019. It's also known as Raspberry Pi 4B (there is no Raspberry Pi 4A). There are three current Raspberry Pi 4 models that are identical, except for the amount of RAM. Cheapest one at 2GB ram, next one at 4GB ram, and highest performing one at 8GB. Even the 2GB one is powerful enough to work as a desktop. 4GB version suffices for almost any application, as Raspbian OS is very memory efficient, and never exceeds 4GB even with bunch of tabs and processes running. It is powered by a new processor, the Broadcom BCM2711B0, which is built in 28nm, and is based on ARM Cortex-A72 core, instead of earlier ARM Cortex A53 core. it has 4 CPU and clock speed of 1.5GHz. It also has a GPU from Broadcom called VideoCore VI, running at 500MHz.

Installation:

To use Raspberry pi, you will need to raspbian as explained in previous "Raspberry" section. Then you install vncserveer for raspbian, so that you can work on the raspberry w/o having a monitor and keyboard. Once vncserver is running, you access raspberry pi using vncviewer from some other laptop. Now you can start working on this great little machine.

Getting started:

Open a terminal, and type "pinout" cmd. This gives info on all the pins on the header that can be used for our purpose.

Setup time  and Hold time:

Any logic you design from RTL, you will always hear about setup time and hold time, when running timing tools. Sequential elements such as flops and latches have setup and hold time requirements, in order to function correctly.Let's explore this important concept.

Origin of setup and hold time:

In broader terms, any circuit that stores value has a back to back inverter or some other back to back gates to hold the value. However such a circuit is not of much use, unless we can control the value that's stored in this back to back inverter. This is done via a signal to enable or disable the write, and another signal which transmits the value to be written into. This signal which enables or disables the write has a timing relation wrt to the signal that we are trying to write in. The value to be written in has to be stable around the time, the enable/disable signal changes, or else we may not store the desired value. This time window where we want our input signal to be stable is called setup time and hold time.

Setup time: The time before the enable signal that the input signal has to be stable

Hold time: The time after the enable signal that the input signal has to be stable

 The sum of "setup_time + hold_time" is the window where the input signal shouldn't change, or else the value stored in the seq element would be unpredictable.

This link does a superb job of explaining them:

https://www.edn.com/understanding-the-basics-of-setup-and-hold-time/

Setup and hold time in circuit paths:

We understand the setup/hold time of flops/latches, etc. But let's say we have a path from 1 flop to other flop. The 2nd flop can't have it's data change within it's setup/hold window.

Let's see a simple 2 bit counter. Violating setup time (too slow) or hold time (too fast) causes counter to have incorrect values as shown in the diagram.

Attach diagram FIXME

Eqn is:

That's why the 0 cycle path is called the hold timing for the path, as "hold time" of the capturing flop is involved in the eqn. Being a 0 cycle path, the freq of the clk has no impact on whether the path meets the "hold time". So, these paths are independent of freq.

Similarly 1 cycle path is called the setup timing for the path, as "setup time" of the capturing flop is involved in the eqn. Being a 1 cycle path, the freq of the clk will impact on whether the path meets the "setup time". So, these paths are dependent on freq, and setup time of the path can be fixed by relaxing the clk freq.

 

Timing Tools applying setup and hold time:

In PT and other timing tools, single cycle paths are easy to analyze. Usually paths are from flop to flop. So, most of the designs have 0 cycle hold paths and 1 cycle setup paths. When we have multiple clks with diff freq, then relationship gets more complex. Discuss MCP, and how setup/hold are calculated by timing tools

 

 

 

 

Deep Learning

Most of the material here is from Andrew Ng's AI course on couesera.org. It's called Deep Learning specialization. Even though it's called Deep Learning, it starts with basic concepts of AI, and then moves to ML, ANN and finally to CNN. It consists of 5 courses as outlined in link below:

https://www.coursera.org/specializations/deep-learning

The 5 courses in deep learning specialization are as follows:

Course 1: Neural Networks and Deep Learning: => Has 4 weeks worth of material, requiring about 20 hrs to complete.

Course 2: Hyperparameter tuning, Regularization and Optimization => Has 3 weeks worth of material, requiring about 18 hrs to complete.

Course 3: Structuring ML projects => Has 2 weeks worth of material, requiring about 5 hrs to complete.

Course 4: Convolutional Neural Networks (CNN) => Has 4 weeks worth of material, requiring about 20 hrs to complete. CNN is the most popular NN

Course 5: Sequence models => Has 3 weeks worth of material, requiring about 15 hrs to complete.

More AI related reserach and info is available on https://www.deeplearning.ai/

Before we go into the course work, we have to get prepared for doing exercises in Python. Without doing exercises and playing around, you will never get a feel of AI. AI is a very fast field, and we will never be able to learn even a little fraction of it, but whatever we learn, we should make sure we learn the basics well.

Installation of Python and various modules:

Below are some of the pgm you will need to install on your computer, before you can do any exercises on coursera. Ofcourse they have Jupyter Notebook for you to work in (Jupyter Notebook migrated to Coursera Lab environment starting Sept, 2020). Jupyter Notebook is an app that allows you to run python and many other pgm languages from within it. However, you may not be able to understand all bits and pieces of how things are working. Also, as these keep changing on Coursera website (i.e you are at the mercy of coursera on how long they continue with which app), I highly encourage you to install python and other needed modules on your local machine (running any Linux OS, I'm running it on CentOS 7), and do all the programming exercises locally. It'll be much more fun. I'm doing it locally myself, so will post all needed info below.

Python: Visit the section under Python programming. Install Python3 (python 3.6 as of July, 2020) as detailed in that section, and then install these other modules.

NumPy: Install NumPy package for python as detailed there, and go thru the basic tutorial

H5py: Install H5py package for python which is used to read data files in HDF5 format. This format is used for our exercises to store large amounts of data.

matplotlib: Install matplotlib module for python and go thru tutorials as explained in that section

PIL: Install Pillow module for python, which we'll use widely for reading images, as compared to matplotlib and scipy. See PIL/Pillow section.

Downloading various local functions and datasets:

There are many functions and datasets that you will see being imported in python pgms on coursera. You do not see them on main notebook page. One way to see all the files being used in python pgm is to go to Jupyter Notebook, and click on File->open on the top of the page. This will take you to a new page, which will show all the folders and files for that programming assignment. There is a "download" button on top, so download all the files that you need (one file at a time, if you try to download multiple files, the download button disappears)