\title{Addition in myHDL (In Progress)}
\author{Steven K Armour}
\maketitle

# Refrances

https://www.geeksforgeeks.org/digital-logic-carry-look-ahead-adder/

http://faculty.kfupm.edu.sa/COE/abouh/Lesson3_3.pdf

# Libraries and Helper functions

```python
from myhdl import *
from myhdlpeek import Peeker
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

from sympy import *
init_printing()

import itertools

#https://github.com/jrjohansson/version_information
%load_ext version_information
%version_information myhdl, myhdlpeek, numpy, pandas, matplotlib, sympy, random, itertools
```

Software	Version

Python	3.6.5 64bit [GCC 7.2.0]

IPython	6.4.0

OS	Linux 4.15.0 30 generic x86_64 with debian buster sid

myhdl	0.10

myhdlpeek	0.0.7

numpy	1.14.3

pandas	0.23.0

matplotlib	2.2.2

sympy	1.1.1

random	The 'random' distribution was not found and is required by the application

itertools	The 'itertools' distribution was not found and is required by the application

Tue Aug 21 12:52:12 2018 MDT

```python
#helper  functions to read in the .v and .vhd generated files into python
def VerilogTextReader(loc, printresult=True):
    with open(f'{loc}.v', 'r') as vText:
        VerilogText=vText.read()
    if printresult:
        print(f'***Verilog modual from {loc}.v***\n\n', VerilogText)
    return VerilogText

def VHDLTextReader(loc, printresult=True):
    with open(f'{loc}.vhd', 'r') as vText:
        VerilogText=vText.read()
    if printresult:
        print(f'***VHDL modual from {loc}.vhd***\n\n', VerilogText)
    return VerilogText
```

```python
BitVals=[0,1]
```

# Half Adder

## myHDL Code

```python
@block
def HalfAdder(x1, x2, s, c):
    """
    Half Adder
    Input:
        x1(bool): bit signal to be added
        x2(bool): bit signal to be added

    Output:
        s(bool): Half Adder Sum
        c(bool): Half Adder Carry

    """
    @always_comb
    def logic():
        s.next=x1 ^ x2
        c.next=x1 & x2

    return instances()
```

## myHDL Testing

```python
Peeker.clear()
x1=Signal(bool(0)); Peeker(x1, 'x1')
x2=Signal(bool(0)); Peeker(x2, 'x2')
s=Signal(bool(0)); Peeker(s, 's')
c=Signal(bool(0)); Peeker(c, 'c')

#generate test values
x1TVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals))]
x2TVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals))]

DUT=HalfAdder(x1, x2, s, c)

def HalfAdder_TB():
    """
    myHDL only Testbench for module "HalfAdder"
    """
    @instance
    def stimules():
        for i, j in zip(x1TVals, x2TVals):
            x1.next=i; x2.next=j
            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, HalfAdder_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom('x1', 'x2', 's', 'c')
```

```python
HalfAdderData=Peeker.to_dataframe()
HalfAdderData=HalfAdderData[['x1', 'x2', 's', 'c']]
HalfAdderData
```

      x1
      x2
      s
      c

      0
      0
      0
      0
      0

      1
      0
      1
      1
      0

      2
      1
      0
      1
      0

      3
      1
      1
      0
      1

```python
HalfAdderData['sCheck']=HalfAdderData.apply(lambda row: row['x1']^row['x2'], axis=1)
HalfAdderData['cCheck']=HalfAdderData.apply(lambda row: row['x1']&row['x2'], axis=1)
HalfAdderData
```

      x1
      x2
      s
      c
      sCheck
      cCheck

      0
      0
      0
      0
      0
      0
      0

      1
      0
      1
      1
      0
      1
      0

      2
      1
      0
      1
      0
      1
      0

      3
      1
      1
      0
      1
      0
      1

```python
SumCheck=(HalfAdderData['s']==HalfAdderData['sCheck']).all()
CarryCheck=(HalfAdderData['c']==HalfAdderData['cCheck']).all()
print(f'Sum Result Check: {SumCheck}; Carry Result Check: {CarryCheck}')
```

    Sum Result Check: True; Carry Result Check: True

## Verilog Code

```python
DUT.convert()
VerilogTextReader('HalfAdder');
```

    ***Verilog modual from HalfAdder.v***

     // File: HalfAdder.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:14 2018

    `timescale 1ns/10ps

    module HalfAdder (
        x1,
        x2,
        s,
        c
    );
    // Half Adder
    // Input:
    //     x1(bool): bit signal to be added
    //     x2(bool): bit signal to be added
    //
    // Output:
    //     s(bool): Half Adder Sum
    //     c(bool): Half Adder Carry

    input x1;
    input x2;
    output s;
    wire s;
    output c;
    wire c;

    assign s = (x1 ^ x2);
    assign c = (x1 & x2);

    endmodule

## Verilog Testbench

```python
#generate test values
x1TVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals))]
x2TVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals))]
#create single value representation of Test Inputs
x1TVal=intbv(int(''.join([str(i) for i in x1TVals]), 2))[len(x1TVals):]
x2TVal=intbv(int(''.join([str(i) for i in x2TVals]), 2))[len(x2TVals):]
```

```python
print(f'x1Test: {x1TVals}, {x1TVal}, {[int(i) for i in x1TVal]}')
print(f'x2Test: {x2TVals}, {x2TVal}, {[int(i) for i in x2TVal]}')
```

    x1Test: [0, 0, 1, 1], 3, [0, 0, 1, 1]
    x2Test: [0, 1, 0, 1], 5, [0, 1, 0, 1]

```python
@block
def HalfAdder_TBV():
    """
    myHDL -> Verilog Testbench for module "HalfAdder"
    """
    x1=Signal(bool(0))
    x2=Signal(bool(0))
    s=Signal(bool(0))
    c=Signal(bool(0))

    #test stimuli
    x1TVals=Signal(x1TVal)
    x2TVals=Signal(x2TVal)

    @always_comb
    def print_data():
        print(x1, x2, s, c)

    DUT=HalfAdder(x1, x2, s, c)

    @instance
    def stimules():
        for i in range(len(x1TVal)):
            x1.next=x1TVals[i]; x2.next=x2TVals[i]
            yield delay(1)

        raise StopSimulation()

    return instances()

TB=HalfAdder_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('HalfAdder_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from HalfAdder_TBV.v***

     // File: HalfAdder_TBV.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:14 2018

    `timescale 1ns/10ps

    module HalfAdder_TBV (

    );
    // myHDL -> Verilog Testbench for module "HalfAdder"

    reg x1 = 0;
    reg x2 = 0;
    wire s;
    wire c;
    wire [3:0] x1TVals;
    wire [3:0] x2TVals;

    assign x1TVals = 4'd3;
    assign x2TVals = 4'd5;

    always @(s, x2, c, x1) begin: HALFADDER_TBV_PRINT_DATA
        $write("%h", x1);
        $write(" ");
        $write("%h", x2);
        $write(" ");
        $write("%h", s);
        $write(" ");
        $write("%h", c);
        $write("\n");
    end

    assign s = (x1 ^ x2);
    assign c = (x1 & x2);

    initial begin: HALFADDER_TBV_STIMULES
        integer i;
        for (i=0; i<4; i=i+1) begin
            x1 <= x1TVals[i];
            x2 <= x2TVals[i];
            # 1;
        end
        $finish;
    end

    endmodule

    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: x1TVals
      category=ToVerilogWarning
    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: x2TVals
      category=ToVerilogWarning

# Full Adder From Exspresion

## myHDL Code

```python
@block
def FullAdder(x1, x2, cin, s, c):
    """
    Full Adder 2bit+1 input from exspresion
    Input:
        x1(bool): bit signal to be added
        x2(bool): bit signal to be added
        cin(bool): carry in bit signal

    Output:
        s(bool): Full Adder Sum
        c(bool): Full Adder Carry

    """
    @always_comb
    def logic():
        s.next=x1^x2^cin
        c.next=(x1&x2) | (x1&cin) | (x2&cin)

    return instances()

```

## myHDL Testing

```python
Peeker.clear()
x1=Signal(bool(0)); Peeker(x1, 'x1')
x2=Signal(bool(0)); Peeker(x2, 'x2')
cin=Signal(bool(0)); Peeker(cin, 'cin')
s=Signal(bool(0)); Peeker(s, 's')
c=Signal(bool(0)); Peeker(c, 'c')

#generate test values
x1TVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]
x2TVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]
cinTVals=[i[2] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]

DUT=FullAdder(x1, x2, cin, s, c)

def FullAdder_TB():
    """
    myHDL only Testbench for module "FullAdder"
    """
    @instance
    def stimules():
        for i, j, k in zip(x1TVals, x2TVals, cinTVals):
            x1.next=i; x2.next=j; cin.next=k
            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, FullAdder_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom('x1', 'x2', 'cin', 's', 'c')
```

```python
FullAdderData=Peeker.to_dataframe()
FullAdderData=FullAdderData[['x1', 'x2', 'cin', 's', 'c']]
FullAdderData
```

      x1
      x2
      cin
      s
      c

      0
      0
      0
      0
      0
      0

      1
      0
      0
      1
      1
      0

      2
      0
      1
      0
      1
      0

      3
      0
      1
      1
      0
      1

      4
      1
      0
      0
      1
      0

      5
      1
      0
      1
      0
      1

      6
      1
      1
      0
      0
      1

      7
      1
      1
      1
      1
      1

```python
FullAdderData['sCheck']=FullAdderData.apply(lambda row: row['x1']^row['x2']^row['cin'], axis=1)
FullAdderData['cCheck']=FullAdderData.apply(lambda row: (row['x1']&row['x2'])|(row['cin']*(row['x1']^row['x2'])), axis=1)
FullAdderData
```

      x1
      x2
      cin
      s
      c
      sCheck
      cCheck

      0
      0
      0
      0
      0
      0
      0
      0

      1
      0
      0
      1
      1
      0
      1
      0

      2
      0
      1
      0
      1
      0
      1
      0

      3
      0
      1
      1
      0
      1
      0
      1

      4
      1
      0
      0
      1
      0
      1
      0

      5
      1
      0
      1
      0
      1
      0
      1

      6
      1
      1
      0
      0
      1
      0
      1

      7
      1
      1
      1
      1
      1
      1
      1

```python
SumCheck=(FullAdderData['s']==FullAdderData['sCheck']).all()
CarryCheck=(FullAdderData['c']==FullAdderData['cCheck']).all()
print(f'Sum Result Check: {SumCheck}; Carry Result Check: {CarryCheck}')
```

    Sum Result Check: True; Carry Result Check: True

## Verilog Code

```python
DUT.convert()
VerilogTextReader('FullAdder');
```

    ***Verilog modual from FullAdder.v***

     // File: FullAdder.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:15 2018

    `timescale 1ns/10ps

    module FullAdder (
        x1,
        x2,
        cin,
        s,
        c
    );
    // Full Adder 2bit+1 input from exspresion
    // Input:
    //     x1(bool): bit signal to be added
    //     x2(bool): bit signal to be added
    //     cin(bool): carry in bit signal
    //
    // Output:
    //     s(bool): Full Adder Sum
    //     c(bool): Full Adder Carry

    input x1;
    input x2;
    input cin;
    output s;
    wire s;
    output c;
    wire c;

    assign s = ((x1 ^ x2) ^ cin);
    assign c = (((x1 & x2) | (x1 & cin)) | (x2 & cin));

    endmodule

## Verilog Testbench

```python
#generate test values
x1TVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]
x2TVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]
cinTVals=[i[2] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]

#create single value representation of Test Inputs
x1TVal=intbv(int(''.join([str(i) for i in x1TVals]), 2))[len(x1TVals):]
x2TVal=intbv(int(''.join([str(i) for i in x2TVals]), 2))[len(x2TVals):]
cinTVal=intbv(int(''.join([str(i) for i in cinTVals]), 2))[len(cinTVals):]
```

```python
print(f'x1Test: {x1TVals}, {x1TVal}, {[int(i) for i in x1TVal]}')
print(f'x2Test: {x2TVals}, {x2TVal}, {[int(i) for i in x2TVal]}')
print(f'cinTest: {cinTVals}, {cinTVal}, {[int(i) for i in cinTVal]}')
```

    x1Test: [0, 0, 0, 0, 1, 1, 1, 1], 0f, [0, 0, 0, 0, 1, 1, 1, 1]
    x2Test: [0, 0, 1, 1, 0, 0, 1, 1], 33, [0, 0, 1, 1, 0, 0, 1, 1]
    cinTest: [0, 1, 0, 1, 0, 1, 0, 1], 55, [0, 1, 0, 1, 0, 1, 0, 1]

```python
@block
def FullAdder_TBV():
    """
    myHDL -> Verilog Testbench for module "FullAdder"
    """
    x1=Signal(bool(0))
    x2=Signal(bool(0))
    cin=Signal(bool(0))
    s=Signal(bool(0))
    c=Signal(bool(0))

    #test stimuli
    x1TVals=Signal(x1TVal)
    x2TVals=Signal(x2TVal)
    cinTVals=Signal(cinTVal)

    @always_comb
    def print_data():
        print(x1, x2, cin, s, c)

    DUT=FullAdder(x1, x2, cin, s, c)

    @instance
    def stimules():
        for i in range(len(x1TVal)):
            x1.next=x1TVals[i]; x2.next=x2TVals[i]; cin.next=cinTVals[i]
            yield delay(1)

        raise StopSimulation()

    return instances()

TB=FullAdder_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('FullAdder_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from FullAdder_TBV.v***

     // File: FullAdder_TBV.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:16 2018

    `timescale 1ns/10ps

    module FullAdder_TBV (

    );
    // myHDL -> Verilog Testbench for module "FullAdder"

    reg x1 = 0;
    reg x2 = 0;
    wire s;
    wire c;
    wire [7:0] x1TVals;
    wire [7:0] x2TVals;
    reg cin = 0;
    wire [7:0] cinTVals;

    assign x1TVals = 8'd15;
    assign x2TVals = 8'd51;
    assign cinTVals = 8'd85;

    always @(x2, c, s, cin, x1) begin: FULLADDER_TBV_PRINT_DATA
        $write("%h", x1);
        $write(" ");
        $write("%h", x2);
        $write(" ");
        $write("%h", cin);
        $write(" ");
        $write("%h", s);
        $write(" ");
        $write("%h", c);
        $write("\n");
    end

    assign s = ((x1 ^ x2) ^ cin);
    assign c = (((x1 & x2) | (x1 & cin)) | (x2 & cin));

    initial begin: FULLADDER_TBV_STIMULES
        integer i;
        for (i=0; i<8; i=i+1) begin
            x1 <= x1TVals[i];
            x2 <= x2TVals[i];
            cin <= cinTVals[i];
            # 1;
        end
        $finish;
    end

    endmodule

    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: x1TVals
      category=ToVerilogWarning
    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: x2TVals
      category=ToVerilogWarning
    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: cinTVals
      category=ToVerilogWarning

# Full Adder via HalfAdders

## myHDL Code

```python
@block
def FullAdderViaHAs(x1, x2, cin, s, c):
    """
    Full Adder 2bit+1 input from Half Adders
    Input:
        x1(bool): bit signal to be added
        x2(bool): bit signal to be added
        cin(bool): carry in bit signal

    Output:
        s(bool): Full Adder Sum
        c(bool): Full Adder Carry

    """
    #create ouput wires from first HA (HA1) to next HA and
    #`CarryLogic`
    s_HA1HA2=Signal(bool(0)); c_HA1CL=Signal(bool(0))
    #create first HA and wire it
    HA1_x1x2=HalfAdder(x1=x1, x2=x2, s=s_HA1HA2, c=c_HA1CL)

    #create output wire for seconed HA (HA2) to `CarryLogic`
    c_HA2CL=Signal(bool(0))
    HA2_HA1cin=HalfAdder(x1=cin, x2=s_HA1HA2, s=s, c=c_HA2CL)

    @always_comb
    def CarryLogic():
        c.next= c_HA1CL|c_HA2CL

    return instances()

```

## myHDL Testing

```python
Peeker.clear()
x1=Signal(bool(0)); Peeker(x1, 'x1')
x2=Signal(bool(0)); Peeker(x2, 'x2')
cin=Signal(bool(0)); Peeker(cin, 'cin')
s=Signal(bool(0)); Peeker(s, 's')
c=Signal(bool(0)); Peeker(c, 'c')

#generate test values
x1TVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]
x2TVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]
cinTVals=[i[2] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]

DUT=FullAdderViaHAs(x1, x2, cin, s, c)

def FullAdderViaHAs_TB():
    """
    myHDL only Testbench for module "FullAdderViaHAs"
    """
    @instance
    def stimules():
        for i, j, k in zip(x1TVals, x2TVals, cinTVals):
            x1.next=i; x2.next=j; cin.next=k
            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, FullAdder_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom('x1', 'x2', 'cin', 's', 'c')
```

```python
FullAdderHAData=Peeker.to_dataframe()
FullAdderHAData=FullAdderHAData[['x1', 'x2', 'cin', 's', 'c']]
FullAdderHAData
```

      x1
      x2
      cin
      s
      c

      0
      0
      0
      0
      0
      0

      1
      0
      0
      1
      1
      0

      2
      0
      1
      0
      1
      0

      3
      0
      1
      1
      0
      1

      4
      1
      0
      0
      1
      0

      5
      1
      0
      1
      0
      1

      6
      1
      1
      0
      0
      1

      7
      1
      1
      1
      1
      1

```python
FullAdderHAData['sCheck']=FullAdderHAData.apply(lambda row: row['x1']^row['x2']^row['cin'], axis=1)
FullAdderHAData['cCheck']=FullAdderHAData.apply(lambda row: (row['x1']&row['x2'])|(row['cin']*(row['x1']^row['x2'])), axis=1)
FullAdderHAData
```

      x1
      x2
      cin
      s
      c
      sCheck
      cCheck

      0
      0
      0
      0
      0
      0
      0
      0

      1
      0
      0
      1
      1
      0
      1
      0

      2
      0
      1
      0
      1
      0
      1
      0

      3
      0
      1
      1
      0
      1
      0
      1

      4
      1
      0
      0
      1
      0
      1
      0

      5
      1
      0
      1
      0
      1
      0
      1

      6
      1
      1
      0
      0
      1
      0
      1

      7
      1
      1
      1
      1
      1
      1
      1

```python
SumCheck=(FullAdderHAData['s']==FullAdderHAData['sCheck']).all()
CarryCheck=(FullAdderHAData['c']==FullAdderHAData['cCheck']).all()
print(f'Sum Result Check: {SumCheck}; Carry Result Check: {CarryCheck}')
```

    Sum Result Check: True; Carry Result Check: True

```python
(FullAdderData==FullAdderHAData).all()
```

    x1        True
    x2        True
    cin       True
    s         True
    c         True
    sCheck    True
    cCheck    True
    dtype: bool

## Verilog Code

```python
DUT.convert()
VerilogTextReader('FullAdderViaHAs');
```

    ***Verilog modual from FullAdderViaHAs.v***

     // File: FullAdderViaHAs.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:18 2018

    `timescale 1ns/10ps

    module FullAdderViaHAs (
        x1,
        x2,
        cin,
        s,
        c
    );
    // Full Adder 2bit+1 input from Half Adders
    // Input:
    //     x1(bool): bit signal to be added
    //     x2(bool): bit signal to be added
    //     cin(bool): carry in bit signal
    //
    // Output:
    //     s(bool): Full Adder Sum
    //     c(bool): Full Adder Carry

    input x1;
    input x2;
    input cin;
    output s;
    wire s;
    output c;
    wire c;

    wire s_HA1HA2;
    wire c_HA1CL;
    wire c_HA2CL;

    assign s_HA1HA2 = (x1 ^ x2);
    assign c_HA1CL = (x1 & x2);

    assign s = (cin ^ s_HA1HA2);
    assign c_HA2CL = (cin & s_HA1HA2);

    assign c = (c_HA1CL | c_HA2CL);

    endmodule

## Verilog Testbench

```python
#generate test values
x1TVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]
x2TVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]
cinTVals=[i[2] for i in itertools.product(BitVals, repeat=len(BitVals)+1)]

#create single value representation of Test Inputs
x1TVal=intbv(int(''.join([str(i) for i in x1TVals]), 2))[len(x1TVals):]
x2TVal=intbv(int(''.join([str(i) for i in x2TVals]), 2))[len(x2TVals):]
cinTVal=intbv(int(''.join([str(i) for i in cinTVals]), 2))[len(cinTVals):]
```

```python
print(f'x1Test: {x1TVals}, {x1TVal}, {[int(i) for i in x1TVal]}')
print(f'x2Test: {x2TVals}, {x2TVal}, {[int(i) for i in x2TVal]}')
print(f'cinTest: {cinTVals}, {cinTVal}, {[int(i) for i in cinTVal]}')
```

    x1Test: [0, 0, 0, 0, 1, 1, 1, 1], 0f, [0, 0, 0, 0, 1, 1, 1, 1]
    x2Test: [0, 0, 1, 1, 0, 0, 1, 1], 33, [0, 0, 1, 1, 0, 0, 1, 1]
    cinTest: [0, 1, 0, 1, 0, 1, 0, 1], 55, [0, 1, 0, 1, 0, 1, 0, 1]

```python
@block
def FullAdderViaHAs_TBV():
    """
    myHDL -> Verilog Testbench for module "FullAdderViaHAs"
    """
    x1=Signal(bool(0))
    x2=Signal(bool(0))
    cin=Signal(bool(0))
    s=Signal(bool(0))
    c=Signal(bool(0))

    #test stimuli
    x1TVals=Signal(x1TVal)
    x2TVals=Signal(x2TVal)
    cinTVals=Signal(cinTVal)

    @always_comb
    def print_data():
        print(x1, x2, cin, s, c)

    DUT=FullAdderViaHAs(x1, x2, cin, s, c)

    @instance
    def stimules():
        for i in range(len(x1TVal)):
            x1.next=x1TVals[i]; x2.next=x2TVals[i]; cin.next=cinTVals[i]
            yield delay(1)

        raise StopSimulation()

    return instances()

TB=FullAdderViaHAs_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('FullAdderViaHAs_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from FullAdderViaHAs_TBV.v***

     // File: FullAdderViaHAs_TBV.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:20 2018

    `timescale 1ns/10ps

    module FullAdderViaHAs_TBV (

    );
    // myHDL -> Verilog Testbench for module "FullAdderViaHAs"

    reg x1 = 0;
    reg x2 = 0;
    wire s;
    wire c;
    wire [7:0] x1TVals;
    wire [7:0] x2TVals;
    reg cin = 0;
    wire [7:0] cinTVals;
    wire FullAdderViaHAs0_0_s_HA1HA2;
    wire FullAdderViaHAs0_0_c_HA1CL;
    wire FullAdderViaHAs0_0_c_HA2CL;

    assign x1TVals = 8'd15;
    assign x2TVals = 8'd51;
    assign cinTVals = 8'd85;

    always @(x2, c, s, cin, x1) begin: FULLADDERVIAHAS_TBV_PRINT_DATA
        $write("%h", x1);
        $write(" ");
        $write("%h", x2);
        $write(" ");
        $write("%h", cin);
        $write(" ");
        $write("%h", s);
        $write(" ");
        $write("%h", c);
        $write("\n");
    end

    assign FullAdderViaHAs0_0_s_HA1HA2 = (x1 ^ x2);
    assign FullAdderViaHAs0_0_c_HA1CL = (x1 & x2);

    assign s = (cin ^ FullAdderViaHAs0_0_s_HA1HA2);
    assign FullAdderViaHAs0_0_c_HA2CL = (cin & FullAdderViaHAs0_0_s_HA1HA2);

    assign c = (FullAdderViaHAs0_0_c_HA1CL | FullAdderViaHAs0_0_c_HA2CL);

    initial begin: FULLADDERVIAHAS_TBV_STIMULES
        integer i;
        for (i=0; i<8; i=i+1) begin
            x1 <= x1TVals[i];
            x2 <= x2TVals[i];
            cin <= cinTVals[i];
            # 1;
        end
        $finish;
    end

    endmodule

    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: x1TVals
      category=ToVerilogWarning
    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: x2TVals
      category=ToVerilogWarning
    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: cinTVals
      category=ToVerilogWarning

# 4 Bit Ripple Carry

## myHDL Code

```python
@block
def RippleCarry4Bit(X1, X2, cin, S, c):
    """
    4 Bit Ripple Carray Adder
    Input:
    Output:
    """

    S_i = [Signal(bool(0)) for _ in range(len(S))]   # Sum bit for each stage.

    #create intercontect wire between FA0 and FA1 and intialze FA0 and wire up
    c_FA0FA1=Signal(bool(0))
    FA0=FullAdder(x1=X1(0), x2=X2(0), cin=cin, s=S_i[0], c=c_FA0FA1)

    c_FA1FA2=Signal(bool(0))
    FA1=FullAdder(x1=X1(1), x2=X2(1), cin=c_FA0FA1, s=S_i[1], c=c_FA1FA2)

    c_FA2FA3=Signal(bool(0))
    FA2=FullAdder(x1=X1(2), x2=X2(2), cin=c_FA1FA2, s=S_i[2], c=c_FA2FA3)

    FA3=FullAdder(x1=X1(3), x2=X2(3), cin=c_FA2FA3, s=S_i[3], c=c)

    #concat bus wires to single bitvec wire
    @always_comb
    def ConCatSum():
        S.next=concat(S_i[3], S_i[2], S_i[1], S_i[0])

    return instances()
```

## myHDL Testing

```python
BitSize=4
np.random.seed(12)
X1TVals=np.random.randint(0, 2**BitSize, 2**BitSize)
X2TVals=np.random.randint(0, 2**BitSize, 2**BitSize)
cinTVals=np.random.randint(0,2,2**BitSize)

print(X1TVals)
print(X2TVals)
print(cinTVals)
```

    [11 11  6 13  1  2  3  3 12  0  6  1  4  5 13  9]
    [ 2 11  6 10  0  5  8 12 13  2  9  3 14  4  3  1]
    [0 1 1 0 0 0 0 0 0 0 1 1 1 1 0 0]

```python
Peeker.clear()
X1=Signal(intbv(0)[BitSize:]); Peeker(X1, 'X1')
X2=Signal(intbv(0)[BitSize:]); Peeker(X2, 'X2')
cin=Signal(bool(0)); Peeker(cin, 'cin')
S=Signal(intbv(0)[BitSize:]); Peeker(S, 'S')
c=Signal(bool(0)); Peeker(c, 'c')

DUT = RippleCarry4Bit(X1=X1, X2=X2, cin=cin, S=S, c=c)

def RippleCarry4Bit_TB():
    @instance
    def stimules():
        for i in range(len(X1TVals)):
            X1.next=int(X1TVals[i])
            X2.next=int(X2TVals[i])
            cin.next=int(cinTVals[i])
            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, RippleCarry4Bit_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom('cin', 'X1', 'X2', 'S', 'c')
```

```python
RC4BData=Peeker.to_dataframe()
RC4BData=RC4BData[['cin', 'X1', 'X2', 'S', 'c']]
RC4BData
```

      cin
      X1
      X2
      S
      c

      0
      0
      11
      2
      13
      0

      1
      1
      11
      11
      7
      1

      2
      1
      6
      6
      13
      0

      3
      0
      13
      10
      7
      1

      4
      0
      1
      0
      1
      0

      5
      0
      2
      5
      7
      0

      6
      0
      3
      8
      11
      0

      7
      0
      3
      12
      15
      0

      8
      0
      12
      13
      9
      1

      9
      0
      0
      2
      2
      0

      10
      1
      6
      9
      0
      1

      11
      1
      1
      3
      5
      0

      12
      1
      4
      14
      3
      1

      13
      1
      5
      4
      10
      0

      14
      0
      13
      3
      0
      1

      15
      0
      9
      1
      10
      0

```python
def RC4B_SCheck(cin, X1, X2):
    S=cin+X1+X2
    if S<16:
        return S
    else:
        return S-16
def RC4B_cCheck(cin, X1, X2):
    S=cin+X1+X2
    if S<16:
        return 0
    else:
        return 1

RC4BData['SCheck']=RC4BData.apply(lambda row: RC4B_SCheck(row['cin'], row['X1'], row['X2']), axis=1)
RC4BData['cCheck']=RC4BData.apply(lambda row: RC4B_cCheck(row['cin'], row['X1'], row['X2']), axis=1)
RC4BData
```

      cin
      X1
      X2
      S
      c
      SCheck
      cCheck

      0
      0
      11
      2
      13
      0
      13
      0

      1
      1
      11
      11
      7
      1
      7
      1

      2
      1
      6
      6
      13
      0
      13
      0

      3
      0
      13
      10
      7
      1
      7
      1

      4
      0
      1
      0
      1
      0
      1
      0

      5
      0
      2
      5
      7
      0
      7
      0

      6
      0
      3
      8
      11
      0
      11
      0

      7
      0
      3
      12
      15
      0
      15
      0

      8
      0
      12
      13
      9
      1
      9
      1

      9
      0
      0
      2
      2
      0
      2
      0

      10
      1
      6
      9
      0
      1
      0
      1

      11
      1
      1
      3
      5
      0
      5
      0

      12
      1
      4
      14
      3
      1
      3
      1

      13
      1
      5
      4
      10
      0
      10
      0

      14
      0
      13
      3
      0
      1
      0
      1

      15
      0
      9
      1
      10
      0
      10
      0

```python
SumCheck=(RC4BData['S']==RC4BData['SCheck']).all()
CarryCheck=(RC4BData['c']==RC4BData['cCheck']).all()
print(f'Sum Result Check: {SumCheck}; Carry Result Check: {CarryCheck}')
```

    Sum Result Check: True; Carry Result Check: True

```python
DUT.convert()
VerilogTextReader('RippleCarry4Bit');
```

    ***Verilog modual from RippleCarry4Bit.v***

     // File: RippleCarry4Bit.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:22 2018

    `timescale 1ns/10ps

    module RippleCarry4Bit (
        X1,
        X2,
        cin,
        S,
        c
    );
    // 4 Bit Ripple Carray Adder
    // Input:
    // Output:

    input [3:0] X1;
    input [3:0] X2;
    input cin;
    output [3:0] S;
    wire [3:0] S;
    output c;
    wire c;

    wire c_FA0FA1;
    wire FullAdder1_0_c;
    wire FullAdder2_c;
    wire S_i [0:4-1];

    assign S_i[0] = ((X1[0] ^ X2[0]) ^ cin);
    assign c_FA0FA1 = (((X1[0] & X2[0]) | (X1[0] & cin)) | (X2[0] & cin));

    assign S_i[1] = ((X1[1] ^ X2[1]) ^ c_FA0FA1);
    assign FullAdder1_0_c = (((X1[1] & X2[1]) | (X1[1] & c_FA0FA1)) | (X2[1] & c_FA0FA1));

    assign S_i[2] = ((X1[2] ^ X2[2]) ^ FullAdder1_0_c);
    assign FullAdder2_c = (((X1[2] & X2[2]) | (X1[2] & FullAdder1_0_c)) | (X2[2] & FullAdder1_0_c));

    assign S_i[3] = ((X1[3] ^ X2[3]) ^ FullAdder2_c);
    assign c = (((X1[3] & X2[3]) | (X1[3] & FullAdder2_c)) | (X2[3] & FullAdder2_c));

    assign S = {S_i[3], S_i[2], S_i[1], S_i[0]};

    endmodule

## Verilog Testbench

### Conversion Issue
Values are not beeing set to `X1TVals` and `X1TVals` in `RippleCarry4Bit_TBV` thus the running the simultion results in High **Z** values for each time stamp

```python
X1TVal=[intbv(int(i))[BitSize:] for i in X1TVals]
X2TVal=[intbv(int(i))[BitSize:] for i in X2TVals]
cinTVal=intbv(int(''.join([str(i) for i in cinTVals]), 2))[len(cinTVals):]

print(X1TVal)
print(X2TVal)
print(cinTVal)
```

    [intbv(11), intbv(11), intbv(6), intbv(13), intbv(1), intbv(2), intbv(3), intbv(3), intbv(12), intbv(0), intbv(6), intbv(1), intbv(4), intbv(5), intbv(13), intbv(9)]
    [intbv(2), intbv(11), intbv(6), intbv(10), intbv(0), intbv(5), intbv(8), intbv(12), intbv(13), intbv(2), intbv(9), intbv(3), intbv(14), intbv(4), intbv(3), intbv(1)]
    603c

```python
@block
def RippleCarry4Bit_TBV():
    """
    myHDL -> Verilog Testbench for module "FullAdderViaHAs"
    """
    X1=Signal(intbv(0)[BitSize:])
    X2=Signal(intbv(0)[BitSize:])
    cin=Signal(bool(0))
    S=Signal(intbv(0)[BitSize:])
    c=Signal(bool(0))

    #test stimuli
    X1TVals=[Signal(i) for i in X1TVal]
    X2TVals=[Signal(i) for i in X2TVal]
    cinTVals=Signal(cinTVal)

    @always_comb
    def print_data():
        print(X1, X2, cin, S, c)

    DUT=RippleCarry4Bit(X1=X1, X2=X2, cin=cin, S=S, c=c)

    @instance
    def stimules():
        for i in range(len(X1TVals)):
            X1.next=X1TVals[i]
            X2.next=X2TVals[i]
            cin.next=cinTVals[i]
            yield delay(1)

        raise StopSimulation()

    return instances()

TB=RippleCarry4Bit_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('RippleCarry4Bit_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from RippleCarry4Bit_TBV.v***

     // File: RippleCarry4Bit_TBV.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:25 2018

    `timescale 1ns/10ps

    module RippleCarry4Bit_TBV (

    );
    // myHDL -> Verilog Testbench for module "FullAdderViaHAs"

    wire [3:0] S;
    wire c;
    reg cin = 0;
    wire [15:0] cinTVals;
    reg [3:0] X1 = 0;
    reg [3:0] X2 = 0;
    wire RippleCarry4Bit0_0_c_FA0FA1;
    wire RippleCarry4Bit0_0_FullAdder1_0_1_c;
    wire RippleCarry4Bit0_0_FullAdder2_0_c;
    wire [3:0] X1TVals [0:16-1];
    wire [3:0] X2TVals [0:16-1];
    wire RippleCarry4Bit0_0_S_i [0:4-1];

    assign cinTVals = 16'd24636;

    always @(c, X1, cin, X2, S) begin: RIPPLECARRY4BIT_TBV_PRINT_DATA
        $write("%h", X1);
        $write(" ");
        $write("%h", X2);
        $write(" ");
        $write("%h", cin);
        $write(" ");
        $write("%h", S);
        $write(" ");
        $write("%h", c);
        $write("\n");
    end

    assign RippleCarry4Bit0_0_S_i[0] = ((X1[0] ^ X2[0]) ^ cin);
    assign RippleCarry4Bit0_0_c_FA0FA1 = (((X1[0] & X2[0]) | (X1[0] & cin)) | (X2[0] & cin));

    assign RippleCarry4Bit0_0_S_i[1] = ((X1[1] ^ X2[1]) ^ RippleCarry4Bit0_0_c_FA0FA1);
    assign RippleCarry4Bit0_0_FullAdder1_0_1_c = (((X1[1] & X2[1]) | (X1[1] & RippleCarry4Bit0_0_c_FA0FA1)) | (X2[1] & RippleCarry4Bit0_0_c_FA0FA1));

    assign RippleCarry4Bit0_0_S_i[2] = ((X1[2] ^ X2[2]) ^ RippleCarry4Bit0_0_FullAdder1_0_1_c);
    assign RippleCarry4Bit0_0_FullAdder2_0_c = (((X1[2] & X2[2]) | (X1[2] & RippleCarry4Bit0_0_FullAdder1_0_1_c)) | (X2[2] & RippleCarry4Bit0_0_FullAdder1_0_1_c));

    assign RippleCarry4Bit0_0_S_i[3] = ((X1[3] ^ X2[3]) ^ RippleCarry4Bit0_0_FullAdder2_0_c);
    assign c = (((X1[3] & X2[3]) | (X1[3] & RippleCarry4Bit0_0_FullAdder2_0_c)) | (X2[3] & RippleCarry4Bit0_0_FullAdder2_0_c));

    assign S = {RippleCarry4Bit0_0_S_i[3], RippleCarry4Bit0_0_S_i[2], RippleCarry4Bit0_0_S_i[1], RippleCarry4Bit0_0_S_i[0]};

    initial begin: RIPPLECARRY4BIT_TBV_STIMULES
        integer i;
        for (i=0; i<16; i=i+1) begin
            X1 <= X1TVals[i];
            X2 <= X2TVals[i];
            cin <= cinTVals[i];
            # 1;
        end
        $finish;
    end

    endmodule

    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: cinTVals
      category=ToVerilogWarning

# N Bit Ripple Carray Adder

```python
@block
def RippleCarryNBit(X1, X2, cin, S, c):
    """
    N Bit Ripple Carray Adder
    Input:
    Output:
    """

    S_i = [Signal(bool(0)) for _ in range(len(S))]   # Sum bit for each stage.
    c_FAiFAj=[Signal(bool(0)) for _ in range(len(S)-1)]

    FAStages=[]

    for i in range(len(S)):
        if i==0:
            FAStages.append(FullAdder(x1=X1(i), x2=X2(i), cin=cin, s=S_i[i], c=c_FAiFAj[i]))
        elif i<(len(S)-1):
            FAStages.append(FullAdder(x1=X1(i), x2=X2(i), cin=c_FAiFAj[i-1], s=S_i[i], c=c_FAiFAj[i]))
        else:
            FAStages.append(FullAdder(x1=X1(i), x2=X2(i), cin=c_FAiFAj[i-1], s=S_i[i], c=c))

    #concat bus wires to single bitvec wire
    @always_comb
    def ConCatSum():
        S.next=concat(*reversed(S_i))

    return instances()
```

## myHDL Testing

```python
BitSize=16
np.random.seed(12)
X1TVals=np.random.randint(0, 2**BitSize, 2**4)
X2TVals=np.random.randint(0, 2**BitSize, 2**4)
cinTVals=np.random.randint(0,2,2**4)

print(X1TVals)
print(X2TVals)
print(cinTVals)
```

    [14155 38555 58758 19709 40177 36482  9475 63427 12108 61872   278 57393
     16308 49029 20109 20953]
    [41378  8651  9846  5194 63872 11637 17512 19660 48397 36946 57433  9507
      4030 48228  4515 40353]
    [0 1 1 0 0 0 0 0 0 0 1 1 1 1 0 0]

```python
Peeker.clear()
X1=Signal(intbv(0)[BitSize:]); Peeker(X1, 'X1')
X2=Signal(intbv(0)[BitSize:]); Peeker(X2, 'X2')
cin=Signal(bool(0)); Peeker(cin, 'cin')
S=Signal(intbv(0)[BitSize:]); Peeker(S, 'S')
c=Signal(bool(0)); Peeker(c, 'c')

DUT = RippleCarryNBit(X1=X1, X2=X2, cin=cin, S=S, c=c)

def RippleCarryNBit_TB():
    @instance
    def stimules():
        for i in range(len(X1TVals)):
            X1.next=int(X1TVals[i])
            X2.next=int(X2TVals[i])
            cin.next=int(cinTVals[i])
            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, RippleCarryNBit_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom('cin', 'X1', 'X2', 'S', 'c')
```

```python
RCNBData=Peeker.to_dataframe()
RCNBData=RCNBData[['cin', 'X1', 'X2', 'S', 'c']]
RCNBData
```

      cin
      X1
      X2
      S
      c

      0
      0
      14155
      41378
      55533
      0

      1
      1
      38555
      8651
      47207
      0

      2
      1
      58758
      9846
      3069
      1

      3
      0
      19709
      5194
      24903
      0

      4
      0
      40177
      63872
      38513
      1

      5
      0
      36482
      11637
      48119
      0

      6
      0
      9475
      17512
      26987
      0

      7
      0
      63427
      19660
      17551
      1

      8
      0
      12108
      48397
      60505
      0

      9
      0
      61872
      36946
      33282
      1

      10
      1
      278
      57433
      57712
      0

      11
      1
      57393
      9507
      1365
      1

      12
      1
      16308
      4030
      20339
      0

      13
      1
      49029
      48228
      31722
      1

      14
      0
      20109
      4515
      24624
      0

      15
      0
      20953
      40353
      61306
      0

```python
def RCNB_SCheck(cin, X1, X2):
    S=cin+X1+X2
    if S<2**BitSize:
        return S
    else:
        return S-(2**BitSize)
def RCNB_cCheck(cin, X1, X2):
    S=cin+X1+X2
    if S<2**BitSize:
        return 0
    else:
        return 1

RCNBData['SCheck']=RCNBData.apply(lambda row: RCNB_SCheck(row['cin'], row['X1'], row['X2']), axis=1)
RCNBData['cCheck']=RCNBData.apply(lambda row: RCNB_cCheck(row['cin'], row['X1'], row['X2']), axis=1)
RCNBData
```

      cin
      X1
      X2
      S
      c
      SCheck
      cCheck

      0
      0
      14155
      41378
      55533
      0
      55533
      0

      1
      1
      38555
      8651
      47207
      0
      47207
      0

      2
      1
      58758
      9846
      3069
      1
      3069
      1

      3
      0
      19709
      5194
      24903
      0
      24903
      0

      4
      0
      40177
      63872
      38513
      1
      38513
      1

      5
      0
      36482
      11637
      48119
      0
      48119
      0

      6
      0
      9475
      17512
      26987
      0
      26987
      0

      7
      0
      63427
      19660
      17551
      1
      17551
      1

      8
      0
      12108
      48397
      60505
      0
      60505
      0

      9
      0
      61872
      36946
      33282
      1
      33282
      1

      10
      1
      278
      57433
      57712
      0
      57712
      0

      11
      1
      57393
      9507
      1365
      1
      1365
      1

      12
      1
      16308
      4030
      20339
      0
      20339
      0

      13
      1
      49029
      48228
      31722
      1
      31722
      1

      14
      0
      20109
      4515
      24624
      0
      24624
      0

      15
      0
      20953
      40353
      61306
      0
      61306
      0

```python
SumCheck=(RCNBData['S']==RCNBData['SCheck']).all()
CarryCheck=(RCNBData['c']==RCNBData['cCheck']).all()
print(f'Sum Result Check: {SumCheck}; Carry Result Check: {CarryCheck}')
```

    Sum Result Check: True; Carry Result Check: True

## Verilog Code

### Conversion Error
there is a conversion error in that the converter does not know how to translate as least the `*` unpacking operator in line 27  of `RippleCarryNBit`

```
S.next=ConcatSignal(*reversed(S_i))
```
There is also a high liklyhood that the converter also does not know how to handle the `reversed` operator

```python
#DUT.convert()
#VerilogTextReader('RippleCarryNBit');
```

## myHDL Testbench (ToDo)
To be done when Testbench conversion is improved

# Carry Look Ahead 4 Bit
https://www.geeksforgeeks.org/digital-logic-carry-look-ahead-adder/

http://faculty.kfupm.edu.sa/COE/abouh/Lesson3_3.pdf

## Stage One
$$p_i=A_i \oplus B_i$$
$$G_i=A_i \cdot B_i$$

### myHDL Code

```python
@block
def CLAS1(x1, x2, p, g):
    """
    Carry Look Ahead Adder Stage One
    Input:
        x1(bool): x1 input
        x2(bool): x2 input

    Output:
        p(bool):carry propagate
        g(bool):carry generate

    """
    @always_comb
    def logic():
        p.next=x1^x2
        g.next=x1&x2
    return instances()
```

### myHDL Testing

```python
Peeker.clear()
x1=Signal(bool(0)); Peeker(x1, 'x1')
x2=Signal(bool(0)); Peeker(x2, 'x2')
p=Signal(bool(0)); Peeker(p, 'p')
g=Signal(bool(0)); Peeker(g, 'g')

#generate test values
x1TVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals))]
x2TVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals))]

DUT=CLAS1(x1, x2, p, g)

def CLAS1_TB():
    """
    myHDL only Testbench for module "CL"
    """
    @instance
    def stimules():
        for i, j in zip(x1TVals, x2TVals):
            x1.next=i; x2.next=j
            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, CLAS1_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom('x1', 'x2', 'p', 'g')
```

```python
CLAS1Data=Peeker.to_dataframe()
CLAS1Data=CLAS1Data[['x1', 'x2', 'p', 'g']]
CLAS1Data
```

      x1
      x2
      p
      g

      0
      0
      0
      0
      0

      1
      0
      1
      1
      0

      2
      1
      0
      1
      0

      3
      1
      1
      0
      1

```python
CLAS1Data['pCheck']=CLAS1Data.apply(lambda row: row['x1']^row['x2'], axis=1)
CLAS1Data['gCheck']=CLAS1Data.apply(lambda row: row['x1']&row['x2'], axis=1)
CLAS1Data
```

      x1
      x2
      p
      g
      pCheck
      gCheck

      0
      0
      0
      0
      0
      0
      0

      1
      0
      1
      1
      0
      1
      0

      2
      1
      0
      1
      0
      1
      0

      3
      1
      1
      0
      1
      0
      1

```python
pCheck=(CLAS1Data['p']==CLAS1Data['pCheck']).all()
gCheck=(CLAS1Data['g']==CLAS1Data['gCheck']).all()
print(f'p Result Check: {pCheck}; g Result Check: {gCheck}')
```

    p Result Check: True; g Result Check: True

### Verilog Code

```python
DUT.convert()
VerilogTextReader('CLAS1');
```

    ***Verilog modual from CLAS1.v***

     // File: CLAS1.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:29 2018

    `timescale 1ns/10ps

    module CLAS1 (
        x1,
        x2,
        p,
        g
    );
    // Carry Look Ahead Adder Stage One
    // Input:
    //     x1(bool): x1 input
    //     x2(bool): x2 input
    //
    // Output:
    //     p(bool):carry propagate
    //     g(bool):carry generate

    input x1;
    input x2;
    output p;
    wire p;
    output g;
    wire g;

    assign p = (x1 ^ x2);
    assign g = (x1 & x2);

    endmodule

### Verilog Testbench

```python
#generate test values
x1TVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals))]
x2TVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals))]
#create single value representation of Test Inputs
x1TVal=intbv(int(''.join([str(i) for i in x1TVals]), 2))[len(x1TVals):]
x2TVal=intbv(int(''.join([str(i) for i in x2TVals]), 2))[len(x2TVals):]
```

```python
print(f'x1Test: {x1TVals}, {x1TVal}, {[int(i) for i in x1TVal]}')
print(f'x2Test: {x2TVals}, {x2TVal}, {[int(i) for i in x2TVal]}')
```

    x1Test: [0, 0, 1, 1], 3, [0, 0, 1, 1]
    x2Test: [0, 1, 0, 1], 5, [0, 1, 0, 1]

```python
@block
def CLAS1_TBV():
    """
    myHDL -> Verilog Testbench for module "CLAS1"
    """
    x1=Signal(bool(0))
    x2=Signal(bool(0))
    p=Signal(bool(0))
    g=Signal(bool(0))

    #test stimuli
    x1TVals=Signal(x1TVal)
    x2TVals=Signal(x2TVal)

    @always_comb
    def print_data():
        print(x1, x2, p, g)

    DUT=CLAS1(x1, x2, p, g)

    @instance
    def stimules():
        for i in range(len(x1TVal)):
            x1.next=x1TVals[i]; x2.next=x2TVals[i]
            yield delay(1)

        raise StopSimulation()

    return instances()

TB=CLAS1_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('CLAS1_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from CLAS1_TBV.v***

     // File: CLAS1_TBV.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:30 2018

    `timescale 1ns/10ps

    module CLAS1_TBV (

    );
    // myHDL -> Verilog Testbench for module "CLAS1"

    reg x1 = 0;
    reg x2 = 0;
    wire [3:0] x1TVals;
    wire [3:0] x2TVals;
    wire p;
    wire g;

    assign x1TVals = 4'd3;
    assign x2TVals = 4'd5;

    always @(p, x2, g, x1) begin: CLAS1_TBV_PRINT_DATA
        $write("%h", x1);
        $write(" ");
        $write("%h", x2);
        $write(" ");
        $write("%h", p);
        $write(" ");
        $write("%h", g);
        $write("\n");
    end

    assign p = (x1 ^ x2);
    assign g = (x1 & x2);

    initial begin: CLAS1_TBV_STIMULES
        integer i;
        for (i=0; i<4; i=i+1) begin
            x1 <= x1TVals[i];
            x2 <= x2TVals[i];
            # 1;
        end
        $finish;
    end

    endmodule

    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: x1TVals
      category=ToVerilogWarning
    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: x2TVals
      category=ToVerilogWarning

## Stage Three
$$S_i=P_i \oplus C_i $$

### myHDL Code

```python
@block
def CLAS3(p, c, s):
    """
    Carrry Look Ahead Adder Stage 3
    Input:
        p(bool):carry propagate
        c(bool): carry

    Output:
        s(bool): sum
    """
    @always_comb
    def logic():
        s.next=p^c
    return instances()
```

### myHDL Testing

```python
Peeker.clear()
p=Signal(bool(0)); Peeker(p, 'p')
c=Signal(bool(0)); Peeker(c, 'c')
s=Signal(bool(0)); Peeker(s, 's')

#generate test values
pTVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals))]
cTVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals))]

DUT=CLAS3(p, c, s)

def CLAS3_TB():
    """
    myHDL only Testbench for module "CLAS3"
    """
    @instance
    def stimules():
        for i, j in zip(pTVals, cTVals):
            p.next=i; c.next=j
            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, CLAS3_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom('p', 'c', 's')
```

```python
CLAS3Data=Peeker.to_dataframe()
CLAS3Data=CLAS3Data[['p', 'c', 's']]
CLAS3Data
```

      p
      c
      s

      0
      0
      0
      0

      1
      0
      1
      1

      2
      1
      0
      1

      3
      1
      1
      0

```python
CLAS3Data['sCheck']=CLAS3Data.apply(lambda row: row['p']^row['c'], axis=1)
CLAS3Data
```

      p
      c
      s
      sCheck

      0
      0
      0
      0
      0

      1
      0
      1
      1
      1

      2
      1
      0
      1
      1

      3
      1
      1
      0
      0

```python
sCheck=(CLAS3Data['s']==CLAS3Data['sCheck']).all()
print(f's Result Check: {sCheck}')
```

    s Result Check: True

### Verilog Code

```python
DUT.convert()
VerilogTextReader('CLAS3');
```

    ***Verilog modual from CLAS3.v***

     // File: CLAS3.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:30 2018

    `timescale 1ns/10ps

    module CLAS3 (
        p,
        c,
        s
    );
    // Carrry Look Ahead Adder Stage 3
    // Input:
    //     p(bool):carry propagate
    //     c(bool): carry
    //
    // Output:
    //     s(bool): sum

    input p;
    input c;
    output s;
    wire s;

    assign s = (p ^ c);

    endmodule

### Verilog Testbench

```python
#generate test values
pTVals=[i[0] for i in itertools.product(BitVals, repeat=len(BitVals))]
cTVals=[i[1] for i in itertools.product(BitVals, repeat=len(BitVals))]
#create single value representation of Test Inputs
pTVal=intbv(int(''.join([str(i) for i in pTVals]), 2))[len(pTVals):]
cTVal=intbv(int(''.join([str(i) for i in cTVals]), 2))[len(cTVals):]
```

```python
print(f'pTest: {pTVals}, {pTVal}, {[int(i) for i in pTVal]}')
print(f'cTest: {cTVals}, {cTVal}, {[int(i) for i in cTVal]}')
```

    pTest: [0, 0, 1, 1], 3, [0, 0, 1, 1]
    cTest: [0, 1, 0, 1], 5, [0, 1, 0, 1]

```python
@block
def CLAS3_TBV():
    """
    myHDL -> Verilog Testbench for module "CLAS3"
    """
    p=Signal(bool(0))
    c=Signal(bool(0))
    s=Signal(bool(0))

    #test stimuli
    pTVals=Signal(pTVal)
    cTVals=Signal(cTVal)

    @always_comb
    def print_data():
        print(p, c, s)

    DUT=CLAS3(p, c, s)

    @instance
    def stimules():
        for i in range(len(pTVal)):
            p.next=pTVals[i]; c.next=cTVals[i]
            yield delay(1)

        raise StopSimulation()

    return instances()

TB=CLAS3_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('CLAS3_TBV');
```

    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    <class 'myhdl._Signal._Signal'> <class '_ast.Name'>
    ***Verilog modual from CLAS3_TBV.v***

     // File: CLAS3_TBV.v
    // Generated by MyHDL 0.10
    // Date: Tue Aug 21 12:52:32 2018

    `timescale 1ns/10ps

    module CLAS3_TBV (

    );
    // myHDL -> Verilog Testbench for module "CLAS3"

    wire s;
    reg c = 0;
    reg p = 0;
    wire [3:0] pTVals;
    wire [3:0] cTVals;

    assign pTVals = 4'd3;
    assign cTVals = 4'd5;

    always @(p, s, c) begin: CLAS3_TBV_PRINT_DATA
        $write("%h", p);
        $write(" ");
        $write("%h", c);
        $write(" ");
        $write("%h", s);
        $write("\n");
    end

    assign s = (p ^ c);

    initial begin: CLAS3_TBV_STIMULES
        integer i;
        for (i=0; i<4; i=i+1) begin
            p <= pTVals[i];
            c <= cTVals[i];
            # 1;
        end
        $finish;
    end

    endmodule

    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: pTVals
      category=ToVerilogWarning
    /home/iridium/anaconda3/lib/python3.6/site-packages/myhdl/conversion/_toVerilog.py:349: ToVerilogWarning: Signal is not driven: cTVals
      category=ToVerilogWarning

## Stage Two
$$C_i+1=G_i+P_i\cdot C_i$$
And is thus a recursion formula, so for a exsample a 4 bit  Carray Lock Ahead Adder would have the following carry exspersions

$$C_1=G_0+P_0\cdot C_{in}$$
$$C_2=G_1+P_1\cdot C_1=G_1+P_1G_0+P_1P_0C_{in}$$
$$C_3=G_2+P_2\cdot C_2=G_2+P_2G_1+P_2P_1G_0+P_2P_1P_0C_{in}$$
$$C_4=G_3+P_3\cdot C_3=G_3+P_3G_2+P_3P_2G_1+P_3P_2P_1G_0+P_3P_2P_1P_0C_{in}$$

### myHDL Code (ToDo)

### myHDL Testing (ToDo)

```python
def CLAS2B4_TestFunc(P, G, cin):
    P=[int(i) for i in reversed(bin(P,4))]
    G=[int(i) for i in reversed(bin(G,4))]
    C=[]
    for i in range(4):
        if i==0:
            C.append(int(G[i]) | (P[i]&cin))
        else:
            C.append(int(G[i]) | (P[i] &C[i-1]))

    C=int(''.join(str(i) for i in C[::-1]),2)
    return C
CLAS2B4_TestFunc(8,6,1)
```

```python

```


 ILI285 - Computación Científica I  / INF285 - Computación Científica

 Interpolation: Splines

  [S]cientific [C]omputing [T]eam

 Version: 1.22

## Table of Contents
* [Introduction](#intro)
* [Splines](#sp)
* [Properties](#pr)
* [Solving](#so)
* [The additional Property](#ad)
* [Exercises](#ex)
* [Acknowledgements](#acknowledgements)

```python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import scipy as sp
from scipy import interpolate
import ipywidgets as widgets
import matplotlib as mpl
mpl.rcParams['font.size'] = 14
mpl.rcParams['axes.labelsize'] = 20
mpl.rcParams['xtick.labelsize'] = 14
mpl.rcParams['ytick.labelsize'] = 14
from scipy.interpolate import CubicSpline
M=8
```

## Introduction

Previously in our jupyter notebooks, we learn about interpolation. Methods like Newton's Divided Difference, Lagrange, among others. Other alternative for interpolate a set of data points is using ** Cubic Splines**.
This technique, avoids the Runge's Phenomenon and creates a 3-degree polynomial easily.

## Splines

The most common spline is the linear spline. Given a set of points $(x_{1},y_{1}), (x_{2},y_{2}),...,(x_{n},y_{n}) $, this spline connects each point creating a non-smooth curve. However, this polynomial haves a problem. It's no smooth curve! For to avoid this problem, **the cubic splines creates a set of 3-degree polynomial (specifically n-1 polynomials)**... a much better curve.

```python
# Code based on Example from: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicSpline.html#scipy.interpolate.CubicSpline

# The data
x = np.linspace(0,2*np.pi,12)
y = np.sin(x)*x

# Building interpolation object
cs = CubicSpline(x, y)

# Defining a finer mesh to plot the function
xx = np.linspace(0,2*np.pi,1000)
yyo = np.sin(xx)*xx
yyo1 = np.cos(xx)*xx+np.sin(xx)
yyo2 = -np.sin(xx)*xx+2*np.cos(xx)
yyo3 = -np.cos(xx)*xx-3*np.sin(xx)
yyo4 = np.sin(xx)*xx-4*np.cos(xx)

#Interpolating the date with the spline
yy = cs(xx)
yy1 = cs(xx, 1)
yy2 = cs(xx, 2)
yy3 = cs(xx, 3)
yy4 = cs(xx, 4)

# Plotting the splines and its derivatives
plt.figure(figsize=(M,M))
plt.plot(x,y,'k.',markersize=20,label=r'Data Points')
plt.plot(xx,yy, linewidth=4, label=r'S$(x)$')
plt.plot(xx,yy1, linewidth=4, label=r'$\frac{d}{dx}$S$(x)$')
plt.plot(xx,yy2, linewidth=4, label=r'$\frac{d^2}{dx^2}$S$(x)$')
plt.plot(xx,yy3, linewidth=4, label=r'$\frac{d^3}{dx^3}$S$(x)$')
plt.plot(xx,yy4, linewidth=4, label=r'$\frac{d^4}{dx^4}$S$(x)$')

plt.plot(xx,yyo4,'k--',linewidth=4, label='test', alpha=0.4)

plt.plot(x,y,'k.',markersize=20)

plt.title(r'Cubic Spline is defined as S$(x)$')
plt.axis('tight')
plt.xlabel(r'$x$')
plt.ylabel(r'$y$')
plt.grid(True)
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.show()

```

```python
plt.figure()
plt.semilogy(np.abs(yy-yyo))
plt.grid(True)
```

The orange curve is generated with cubic splines (using the scipy implementation). The other colors are the **derivatives** of the Cubic Spline as indicated in the legend.
However, if we thinks about this curve, we can say that exists an **infinitely many** quantity of polynomials such that meets all the points. Our goal is to create an unique polynomial. Given this condition, there is 4 properties that defines the cubic spline we are looking for.

## Properties of Splines

When we want creates a spline of **n** data points, we obtains a set of **n-1** 3-degree polynomials. For example:
Given a set of points $(x_{1},y_{1}), (x_{2},y_{2}),...,(x_{n},y_{n})$, the splines is:

\begin{equation} S_{1}(x) = y_{1} + b_{1}(x-x_{1}) + c_{1}(x-x_{1})^{2} + d_{1}(x-x_{1})^{3} \\
                 S_{2}(x) = y_{2} + b_{2}(x-x_{2}) + c_{2}(x-x_{2})^{2} + d_{2}(x-x_{2})^{3} \\
                 ... \\
                 ... \\
                 ... \\
                 S_{n-1}(x) = y_{n-1} + b_{n-1}(x-x_{n-1}) + c_{n-1}(x-x_{n-1})^{2} + d_{n-1}(x-x_{n-1})^{3}
\end{equation}

Thus, our goal is obtains the $y, b, c$ and $d$ coefficients. With this values, we are creating the spline $S(x)$ that meets all the data points. This spline have the next properties:

## Property 1 (Are the points connected?)

The first property checks if each x-coordinate reachs the correspondant y-coordinate in the spline $S(x)$. Thus, checks if the spline meets the data points.

$$S_{i}(x_{i}) = y_{i}$$ $$ S_{i}(x_{i+1}) = y_{i+1}$$

$$i \in [1,n-1]$$

## Property 2 (Slope Continuity)

The second property ensures that the slopes of the splines at the points' neighborhood to be equals. This guarantees the smoothly of $S(x)$

$$S'_{i-1}(x_{i}) = S'_{i}(x_{i})$$

$$i \in [2,n-1]$$

## Property 3 (Curvature Continuity)

This property ensure that the curvature between differents polynomials will be equal at the neighborhood of points, avoiding abrupt changes of the curve at the data points.

$$S''_{i-1}(x_{i}) = S''_{i}(x_{i})$$

$$i \in [2,n-1]$$

## Solving the system

If we have **n points**, we know that our splines will be composed of **n-1 curves** $S_{i}(x)$. We have too, **(3n-3) unknowns variables** ($b_{i}, c_{i}, d_{i}$ for each spline). However, we can build a system of equations for find this variables. How can i do this? Easy.. Using the previous properties!

Using the previously defined splines for n points:
\begin{equation} S_{1}(x) = y_{1} + b_{1}(x-x_{1}) + c_{1}(x-x_{1})^{2} + d_{1}(x-x_{1})^{3} \\
                 S_{2}(x) = y_{2} + b_{2}(x-x_{2}) + c_{2}(x-x_{2})^{2} + d_{2}(x-x_{2})^{3} \\
                 \vdots \\
                 S_{n-1}(x) = y_{n-1} + b_{n-1}(x-x_{n-1}) + c_{n-1}(x-x_{n-1})^{2} + d_{n-1}(x-x_{n-1})^{3}
\end{equation}

We need too, the first derivatives of this curves:
\begin{equation} S'_{1}(x) = b_{1} + 2c_{1}(x-x_{1}) + 3d_{1}(x-x_{1})^{2} \\
                 S'_{1}(x) = b_{2} + 2c_{2}(x-x_{2}) + 3d_{2}(x-x_{2})^{2} \\
                 \vdots \\
                 S'_{n-1}(x) = b_{n-1} + 2c_{n-1}(x-x_{n-1}) + 3d_{n-1}(x-x_{n-1})^{2} \\
\end{equation}

And its second derivatives:
\begin{equation} S''_{1}(x) = 2c_{1} + 6d_{1}(x-x_{1}) \\
                 S''_{2}(x) = 2c_{2} + 6d_{2}(x-x_{2}) \\
                 \vdots \\
                 S''_{n-1}(x) = 2c_{n-1} + 6d_{n-1}(x-x_{n-1}) \\
\end{equation}

Using the first property, we get **(n-1) equations**:

\begin{equation} b_{1}(x_{2}-x_{1}) + c_{1}(x_{2}-x_{1})^2 + d_{1}(x_{2}-x_{1})^3 = y_{2} - y_{1} \hspace{1cm}(1)\\
                 b_{2}(x_{3}-x_{2}) + c_{2}(x_{3}-x_{2})^2 + d_{2}(x_{3}-x_{2})^3 = y_{3} - y_{2} \hspace{1cm}(2)\\
                 \vdots\\
                 b_{n-1}(x_{n}-x_{n-1}) + c_{n-1}(x_{n}-x_{n-1})^2 + d_{n-1}(x_{n}-x_{n-1})^3 = y_{n} - y_{n-1} \hspace{1cm}(n-1)
\end{equation}

Using the second property, we get **(n-2) equations**:

\begin{equation} b_{1}+2c_{1}(x_{2}-x_{1}) + 3d_{1}(x_{2}-x_{1})^2 - b_{2}= 0 \hspace{1cm}(1)\\
                 b_{2}+2c_{2}(x_{3}-x_{2}) + 3d_{2}(x_{3}-x_{2})^2 - b_{3}=  0 \hspace{1cm}(2)\\
                 \vdots\\
                 b_{n-2}+2c_{n-2}(x_{n-1}-x_{n-2}) + 3d_{n-2}(x_{n-1}-x_{n-2})^2 -b_{n-1}=0  \hspace{1cm}(n-2)\\
\end{equation}

Using the third property, we get **(n-2) equations**:

\begin{equation} 2c_{1}+6d_{1}(x_{2}-x_{1}) - 2c_{2} = 0 \hspace{1cm}(1)\\
                 2c_{2}+6d_{2}(x_{3}-x_{2}) - 2c_{3}=0 \hspace{1cm}(2)\\
                 \vdots\\
                 2c_{n-2}+6d_{n-2}(x_{n-1}-x_{n-2}) - 2c_{n-1} = 0 \hspace{1cm}(n-2)\\
\end{equation}

If we adds all our equations, we obtains **(3n-5) equations**. Clearly, the matrix in that system is not square (we need 2 equations more). For this, **we have another property**, that defines the edges conditions of the splines

# Splines Ending options

For this special property, we have the following 5 properties:

## Natural Spline:
This property create a spline with zero curvature, thus:
\begin{align*}
S''_{1}(x_{1}) &= 2c_{1} = 0\\
S''_{n-1}(x_{n}) &= 2c_{n-1}+6d_{n-1}(x_{n}-x_{n-1}) = 0
\end{align*}

## Adjusted curvature:
This property create a spline which curvature is equal to a parameter previously defined, not necessarily zero.
\begin{align*}
S''_{1}(x_{1}) &= 2c_{1} = \kappa_{1}\\
S''_{n-1}(x_{n}) &= 2c_{n-1}+6d_{n-1}(x_{n}-x_{n-1}) = \kappa_{2}
\end{align*}

## Clamped cubic spline:
This property adjust the slopes at the edges of splines to a value previously defined
\begin{align*}
S'_{1}(x_{1}) & = b_{1} = p_{1} \\
S'_{n-1}(x_{n}) & = b_{n-1}+2c_{n-1}(x_{n}-x_{n-1}) + 3d_{n-1}(x_{n}-x_{n-1})^2  = p_{2}
\end{align*}

## Ended Parabolically
With this property, the edges of the splines are 2-degree polynomials. Hence the coefficients:
\begin{align*}
d_{1} &= 0 \\
d_{n-1} &= 0
\end{align*}

- Why does this property does not work for $n\le 3$?

## Not-a-Knot cubic spline
This condition, checks the continuity at the edges, for the third derivative:
\begin{align*}
S'''_{1}(x_{2}) &= S'''_{2}(x_{2})\\
6d_{1}&=6d_{2}\\
S'''_{n-2}(x_{n-1}) &= S'''_{n-1}(x_{n-1})\\
6d_{n-2}&=6d_{n-1}
\end{align*}

- Why does this property does not work for $n\le 4$?

Each property give us the 2 equations needed. Thanks to this, we have **(3n-3) unknowns and equations**
Finally, for to find the coefficients of the spline, we''ll build the system of equations.

## Example:
- If we have the following 3 points: $(x_1,y_1),(x_1,y_1),(x_3,y_3)$
- We will get 6 unknowns
- So, to build a Natural spline, we need to solve the following linear system of equation:

$$ \begin{bmatrix} (x_2-x_1) & (x_2-x_1)^2 & (x_2-x_1)^3 & 0 & 0 & 0 \\
                  0 & 0 & 0 &(x_3-x_2) & (x_3-x_2)^2 & (x_3-x_2)^3  \\
                  1 & 2(x_2-x_1) & 3(x_2-x_1)^2 & -1 & 0 & 0 \\
                  0 & 2 & 6(x_2-x_1) & 0 & -2 & 0 \\
                  0 & 2 & 0 & 0 & 0 & 0 \\
                  0 & 0 & 0 & 0 & 2 & 6(x_3-x_2) \\
                  \end{bmatrix}
                  \left[ \begin{array}{c} b_1 \\ c_1 \\ d_1 \\ b_2 \\ c_2 \\ d_2 \end{array} \right] =
                  \left[ \begin{array}{c} y_2-y_1 \\ y_3-y_2 \\ 0 \\ 0 \\ 0 \\ 0  \end{array} \right]
$$

Finally, find the coefficients of splines is reduced to solve an equation system, and we already know this from previous notebooks!

Now the code:

```python
def cubic_spline(x, y, end=None, k1=0, k2=0, p1=0, p2=0):
    #x: x-coordinates of points
    #y: y-coordinates of points
    #end: Natural, Adjusted, Clamped, Parabolically, NaK

    n = len(x)
    A = np.zeros((3*n-3, 3*n-3))
    b = np.zeros(3*n-3)

    delta_x=np.diff(x)

    #Building the linear system of equations

    #1st property
    for i in np.arange(n-1):
        b[i]= y[i+1]-y[i]
        A[i,3*i:3*(i+1)] = [delta_x[i],delta_x[i]**2,delta_x[i]**3]
    #2nd property
    for i in np.arange(n-2):
        A[(n-1)+i,3*i:3*(i+1)+1]=[1, 2*delta_x[i], 3*delta_x[i]**2, -1]
    #3rd property
    for i in np.arange(n-2):
        A[(n-1)+(n-2)+i,3*i:3*(i+1)+2] = [0, 2, 6*delta_x[i], 0, -2]

    #Ending conditions (4th property)
    if end =='Natural':
        A[-2,1]= 2
        A[-1,-2] = 2
        A[-1,-1] = 6*delta_x[-1]

    elif end == 'Adjusted':
        A[-2,1]= 2
        A[-1,-2] = 2
        A[-1,-1] = 6*delta_x[-1]
        b[-2:] = [k1,k2]
        print('Adjusted',b[-2:])

    elif end == 'Clamped':
        A[-2,0]=1
        A[-1,-3:] = [1,2*delta_x[-1],3*delta_x[-1]**2]
        b[-2:] = [p1,p2]

    elif end == 'Parabolically':
        A[-2,2]=1
        A[-1,-1]=1

    elif end == 'NaK':
        A[-2,2:6]=[6,0,0,-6]
        A[-1,-4:]=[6,0,0,-6]

    #Solving the system
    sol = np.linalg.solve(A,b)
    S = {'b':sol[::3],
         'c':sol[1::3],
         'd':sol[2::3],
         'x':x,
         'y':y
        }
    return S

# 'der' computes the 'der'-derivative of the Spline,
# but it has not been implemented. Can you do it? Please do it!
def cubic_spline_eval(xx,S,der=0):
    x=S['x']
    y=S['y']
    b=S['b']
    c=S['c']
    d=S['d']
    n=len(x)
    yy=np.zeros_like(xx)
    for i in np.arange(n-1):
        jj = np.where(np.logical_and(x[i]<=xx,xx<=x[i+1]))
        yy[jj]=y[i]+b[i]*(xx[jj]-x[i])+c[i]*(xx[jj]-x[i])**2+d[i]*(xx[jj]-x[i])**3
    return yy
```

## Example 1 - Hand made interpolation

```python
x = np.array([1,2,4,5])
y = np.array([2,1,4,3])
S = cubic_spline(x,y,end='Natural')

x1 = np.linspace(1,2,200)
x2 = np.linspace(2,4,200)
x3 = np.linspace(4,5,200)

S1 = y[0]+S['b'][0]*(x1-x[0])+S['c'][0]*(x1-x[0])**2+S['d'][0]*(x1-x[0])**3
S2 = y[1]+S['b'][1]*(x2-x[1])+S['c'][1]*(x2-x[1])**2+S['d'][1]*(x2-x[1])**3
S3 = y[2]+S['b'][2]*(x3-x[2])+S['c'][2]*(x3-x[2])**2+S['d'][2]*(x3-x[2])**3

plt.figure(figsize=(M,M))
plt.plot(x,y,'k.',markersize=20,label='Data Points')
plt.plot(x1,S1,'b',linewidth=5,label=r'S$1(x)$')
plt.plot(x2,S2,'g',linewidth=5,label=r'S$2(x)$')
plt.plot(x3,S3,'r',linewidth=5,label=r'S$2(x)$')
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.grid(True)
plt.show()
```

## Example 2

```python
def show_spline(type_ending='Natural',k1=0, k2=0, p1=0, p2=0):
    x = np.array([1,2,4,5,7,9])
    y = np.array([2,1,4,3,3,4])
    xx=np.linspace(np.min(x),np.max(x),1000)
    S = cubic_spline(x,y,end=type_ending, k1=k1, k2=k2, p1=p1, p2=p2)
    plt.figure(figsize=(M,M))
    plt.plot(xx,cubic_spline_eval(xx,S),'-',linewidth=5,label=r'S$(x)$')
    plt.plot(x,y,'k.',markersize=20,label='Data Points')
    plt.xlabel('$x$')
    plt.ylabel('$y$')
    plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
    plt.grid(True)
    plt.show()
widgets.interact(show_spline, type_ending=['Natural','Adjusted','Clamped','Parabolically','NaK'],
                k1=(-20,20,1),k2=(-20,20,1),p1=(-2,2,0.2),p2=(-2,2,0.2))
```

    interactive(children=(Dropdown(description='type_ending', options=('Natural', 'Adjusted', 'Clamped', 'Paraboli…

    <function __main__.show_spline(type_ending='Natural', k1=0, k2=0, p1=0, p2=0)>

## Exercises
Now, a few questions about splines:
- Create the Clamped Cubic Spline given $(0,0), (1,1), (2,2)$. Consider  $S'_1(x_1) = 0$ y $S'_3(x_3) = 1$. Plot it.
- Can you add another point without recalculating all the coefficients again?
- Implement the 'der' input of the 'cubic_spline_eval' function so we can visualize the derivatives of the spline.
- Add the plots of all the derivatives you consider can be computed into Example 2.

## References

### Scipy Cubic Spline Interpolation
- https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.CubicSpline.html#scipy.interpolate.CubicSpline

# Acknowledgements
* _Material created by professor Claudio Torres_ (`ctorres@inf.utfsm.cl`) _and assistans: Laura Bermeo, Alvaro Salinas, Axel Simonsen and Martín Villanueva. DI UTFSM. April 2016._
* _Updated by Claudio Torres_ (`ctorres@inf.utfsm.cl`). _DI UTFSM. June 2017._
* _Updated by Claudio Torres_ (`ctorres@inf.utfsm.cl`). _DI UTFSM. May 2018._
* _Update July 2020 - v1.22 - C.Torres_ : Fixing formatting issues.

```python

```


```python
import pandas as pd
import numpy as np
import ZebraLib as zb

df = pd.read_excel('D:/UNESP/PythonImagineers/Termo/ideal_air_data.xlsx')
df
```

      T
      h
      u
      s
      pr
      vr

      0
      200
      199.97
      142.56
      1.29559
      0.3363
      1707.000

      1
      210
      209.97
      149.69
      1.34444
      0.3987
      1512.000

      2
      220
      219.97
      156.82
      1.39105
      0.4690
      1346.000

      3
      230
      230.02
      164.00
      1.43557
      0.5477
      1205.000

      4
      240
      240.02
      171.13
      1.47824
      0.6355
      1084.000

      ...
      ...
      ...
      ...
      ...
      ...
      ...

      117
      2050
      2314.60
      1726.80
      3.83030
      2303.0000
      2.555

      118
      2100
      2377.40
      1775.30
      3.86050
      2559.0000
      2.356

      119
      2150
      2440.30
      1823.80
      3.89010
      2837.0000
      2.175

      120
      2200
      2503.20
      1872.40
      3.91910
      3138.0000
      2.012

      121
      2250
      2566.40
      1921.30
      3.94740
      3464.0000
      1.864

122 rows × 6 columns

```python
R = df['T'].values
```

```python
R[0]
```

    200

```python
df[df['T']==200]
```

      T
      h
      u
      s
      pr
      vr

      0
      200
      199.97
      142.56
      1.29559
      0.3363
      1707.0

```python
df[df['T']==200]['h'].values
```

    array([199.97])

```python
float(df[df['T']==200]['h'].values)
```

    199.97

```python
import air
air.linear_regression(['vr', 35.73])
```

      T
      h
      u
      s
      pr
      vr

      0
      887.012
      918.3748
      663.7524
      2.8324
      71.045
      35.73

```python
from sympy import *
from sympy.abc import *
import numpy as np

Ks, Kw, Fmin, Fmax, Fm, Sf, Fa = symbols("K_s K_w F_{min} F_{max} F_m S_f F_a")

Y = ((d**(b+2))*0.67*np.pi*A)
Z = (Y/(8*C)) - Ks*Fmin
X = Ks*(Fm-Fmin) + (1.34*((A*d**b)/Sf)-1)*Kw*Fa
```

```python
X.subs({C:8, d:5, b:-0.1625, Fmin:100, Ks:1.0625, Kw:1.1853, Fa:75, Fm:175, Sf:310, A:2153.5})
```

$\displaystyle 627.871366196144$

```python
e = Z/X
e
```

$\displaystyle \frac{\frac{0.263108384738145 A d^{b + 2}}{C} - F_{min} K_{s}}{F_{a} K_{w} \left(\frac{1.34 A d^{b}}{S_{f}} - 1\right) + K_{s} \left(F_{m} - F_{min}\right)}$

```python
er = e.subs({C:8, d:5, b:-0.1625, Fmin:100, Ks:1.0625, Kw:1.1853, Fa:75, Fm:175, Sf:310, A:2153.5})
er.evalf(4).simplify()
```

$\displaystyle 2.002$

```python
solve(er)
```

    []

```python

```


```python
import numpy as np
import sunode
import sympy as sym
import matplotlib.pyplot as plt
import pandas as pd

import sunode.symode.paramset
import sunode.symode.problem
import sunode.wrappers.as_theano

import theano
import theano.tensor as tt
```

    WARNING (theano.configdefaults): install mkl with `conda install mkl-service`: No module named 'mkl'

### Define ode using sympy and theano

```python
b = tt.dvector('b')
d = tt.dvector('d')

def rhs(t, y, params):
    return {
        'a': params.c.d * y.a + params.f[20],
        'b': {
            'c': [3., 4.],
        }
    }

solution, problem, solver = sunode.wrappers.as_theano.solve_ivp(
    t0=0,
    y0={
        'a': (np.arange(3, dtype=float) + d[0] ** 2, 3),
        'b': {
            'c': (b ** 3, 2),
        }
    },
    params={
        'c': {
            'd': (d, 3),
        },
        'f': np.linspace(0, 1, 50),
    },
    tvals=np.arange(20) / 100,
    rhs=rhs,
)
```

```python
problem.make_jac_dense(debug=True)
```

    import numba
    import numpy
    import scipy

    @numba.njit(fastmath=True)
    def logaddexp(a, b):
        min_val = fmin(a, b)
        max_val = fmax(a, b)
        return max_val + log1p(exp(min_val - max_val))

    @numba.njit(fastmath=True, error_model='numpy')
    def compute(_out, time, state, params):
        _out[:] = 0
        _out[0, 0] = params.c.d[0,]
        _out[1, 1] = params.c.d[1,]
        _out[2, 2] = params.c.d[2,]

    CPUDispatcher(<function SympyOde.make_jac_dense.<locals>.jac_dense at 0x7f533f394200>)

```python
problem.make_sundials_jac_dense()
```

    <Numba C callback 'Ode.make_sundials_jac_dense.<locals>.jac_dense_wrapper'>

```python
tvals = np.arange(20) / 100
t0 = 0
y0 = np.ones(problem.n_states)
solver.set_params(np.ones((), dtype=problem.params_dtype))
```

```python
y_out, grad_out, lamda_out = solver.make_output_buffers(tvals)
grads = np.ones_like(y_out)
```

```python
%%timeit
solver.solve_forward(t0, tvals, y0, y_out)
solver.solve_backward(tvals[-1], t0, tvals, grads, grad_out, lamda_out)
```

    1.25 ms ± 15.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

```python
val = (solution ** 2).sum()
grads = tt.grad(val, [b, d])
```

```python
func = theano.function([b, d], [val] + grads)
func2 = theano.function([b, d], [val])
```

```python
np.random.seed(42)
b = np.random.randn(2)
d = np.random.randn(3)
```

```python
%timeit func(b, d)
%timeit func2(b, d)
```

    1.55 ms ± 2.63 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    180 µs ± 324 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

```python
func(b, d)
```

    [array(185.95454144),
     array([12.06638293,  0.86567236]),
     array([252.23687613,  12.10402814,  21.63579496])]

```python
h = 1e-6
b_ = b.copy()
b_[0] += h
(func(b_, d)[0] - func(b, d)[0]) / h
```

    12.06641837825373

```python
h = 1e-6
d_ = d.copy()
d_[2] += h
(func(b, d_)[0] - func(b, d)[0]) / h
```

    21.63579725333875

### Define ode using sympy

```python
coords = {
    'position': pd.Index(['pos1', 'pos2', 'pos3'])
}

# Define parameters of the ode.
params = {
    'a': (),
    'b': (),
    'c': (),
    'd': (),
    'f': {
        'g': 'position'
    }
}

# Declare state variables
states = {
    'x': {
        'a': (),
        'b': (0,),
    },
    'y': 'position',
}

# Define the right hand side of the ode
def rhs_sympy(t, y, params):
    return {
        'x': {
            'a': y.y[0] * params.d,
            'b': [],
        },
        'y': {
            'pos1': 0,
            'pos2': 1,
            'pos3': 2,
        }
    }

ode = sunode.symode.problem.SympyOde(params, states, rhs_sympy, [('d',), ('a',)], coords=coords)
```

```python
func = ode.make_rhs()
```

```python
p = ode.make_user_data()
y = np.ones((1,), dtype=ode.state_dtype)[0]
out = y[None].view(np.float64).copy()
```

```python
%timeit func(out, 0.1, y, p)
```

    2.04 µs ± 13.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

```python
func_ = list(func.overloads.values())[0]
```

```python
import numba
```

```python
p = ode.make_user_data()
state_dtype = ode.state_dtype
y = np.ones((1,), dtype=state_dtype)[0]
out = np.zeros(4,)

@numba.njit
def foo(out):
    foo = out.view(state_dtype)
    return foo.x.a.sum()
    for _ in range(1000):
        func(out, 0.1, y, p)
    return out.sum()
```

```python
foo(out)
```

    0.0

```python
p = ode.make_user_data()
state_dtype = ode.state_dtype
y = np.ones((1,), dtype=state_dtype)[0]
out = np.zeros(4,)

@numba.njit
def foo(out):
    for _ in range(1000):
        func(out, 0.1, y, p)
    return out.sum()
```

```python
tvals = np.linspace(0, 10, 20)
y0 = np.ones(ode.n_states)
```

### Solve without gradients

```python
import numba
```

```python
solver = sunode.solver.Solver(ode)
solver.set_params_dict({
    'a': 99,
    'b': 0.22,
    'c': 0.11,
    'd': 11,
    'f': {
        'g': np.array([1., 1., 1.])
    }
})
out = solver.make_output_buffers(tvals)
solver.solve(0, tvals, y0, out)
%timeit solver.solve(0, tvals, y0, out)
plt.plot(out);
```

```python
y0.view(ode.state_dtype)[0]
```

    ((1., []), [1., 1., 1.])

### Solve with sensitivities

```python
sens0 = np.zeros((ode.n_params, ode.n_states))
```

```python
solver = sunode.solver.Solver(ode, compute_sens=True, sens_mode='simultaneous')
solver.set_params_dict({
    'a': 99,
    'b': 0.22,
    'c': 0.11,
    'd': 11,
    'f': {
        'g': np.array([1., 1., 1.])
    }
})
out, sens_out = solver.make_output_buffers(tvals)
%timeit solver.solve(0, tvals, y0, out, sens0=sens0, sens_out=sens_out)
plt.plot(sens_out[:, 1, 0]);
```

### Solve with adjoint

```python
solver = sunode.solver.AdjointSolver(ode)
out, grad_out, lamda_out = solver.make_output_buffers(tvals)
solver.set_params_dict({
    'a': 99,
    'b': 0.22,
    'c': 0.11,
    'd': 11,
    'f': {
        'g': np.array([1., 1., 1.])
    }
})

grads = np.ones(out.shape)
grads[:, 0] = 0
```

```python
%%timeit
solver.solve_forward(0, tvals, y0, out)
solver.solve_backward(tvals[-1], 0, tvals, grads, grad_out, lamda_out)
```

    265 µs ± 1.17 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

```python
#solver.as_xarray(tvals, out)
```

### Integrate into theano and check gradients

```python
tvals = np.arange(1, 10)
```

```python
params = tt.dvector('params')
y0 = tt.dvector('y0')
solve_ode = sunode.wrappers.as_theano.SolveODEAdjoint(solver, 0, tvals)
solution = solve_ode(y0, params)

loss = (solution ** 2).sum()
grad_p, grad_y0 = tt.grad(loss, [params, y0])
func = theano.function([y0, params], [loss, grad_p, grad_y0])

# Explicit solution
loss = ((
    ((0.5 * tvals ** 2 * params[1] + tvals * y0[1]) + y0[0]) ** 2
    + (tvals * params[1] + y0[1]) ** 2
)).sum()
grad_p, grad_y0 = tt.grad(loss, [params, y0])

func2 = theano.function([y0, params], [loss, grad_p, grad_y0])
```

```python
p = np.random.randn(3)
y0 = np.random.randn(2)
func(y0, p)
```

    [array(2799.56607648),
     array([   0.        , 6787.19817836,    0.        ]),
     array([ 241.49532577, 1759.40589498])]

```python
func2(y0, p)
```

    [array(2799.56607648),
     array([   0.        , 6787.19817831,    0.        ]),
     array([ 241.49532577, 1759.40589498])]

```python
eps = 1e-8
h = np.zeros_like(y0)
h[0] += eps
a = func(y0, p)
b = func(y0 + h, p)
```

```python
print((b[0] - a[0]) / h)
print(a[2])
```

    [241.4951723         inf]
    [ 241.49532577 1759.40589498]

```python
eps = 1e-8
h = np.zeros_like(p)
h[1] += eps
a = func(y0, p)
b = func(y0, p + h)
```

```python
print((b[0] - a[0]) / h)
print(a[1])
```

    [          inf 6787.19829921           inf]
    [   0.         6787.19817836    0.        ]

### Use in pymc3

```python
import pymc3 as pm

with pm.Model() as model:
    params = pm.Normal('params', sd=10, shape=ode.n_params)
    y0 = pm.Normal('y0', shape=ode.n_states)

    solve_ode = sunode.wrappers.as_theano.SolveODEAdjoint(solver, 0, tvals)
    mu = solve_ode(y0, params)
    error = 0.8 * np.random.randn(len(tvals))
    pm.Normal('y', mu=mu[:, 0], sd=0.8, observed=tvals ** 2 + tvals + 5 + error)

    trace = pm.sample(chains=2, cores=2)
```

    Auto-assigning NUTS sampler...
    Initializing NUTS using jitter+adapt_diag...
    Multiprocess sampling (2 chains in 2 jobs)
    NUTS: [y0, params]
    Sampling 2 chains, 0 divergences: 100%|██████████| 2000/2000 [00:33<00:00, 59.19draws/s]

```python
with model:
    tr = pm.sample_posterior_predictive(trace)
```

    100%|██████████| 1000/1000 [00:00<00:00, 1691.56it/s]

```python
plt.plot(tr['y'].T, color='grey', alpha=0.01);
```

```python
import seaborn as sns
```

```python
sns.distplot(trace['params'][:, 1])
```

```python
sns.distplot(trace['y0'][:, 0])
```