\title{Digital Arithmetic Cells with myHDL} \author{Steven K Armour} \maketitle


In [29]:
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 random

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


The version_information extension is already loaded. To reload it, use:
  %reload_ext version_information
Out[29]:
SoftwareVersion
Python3.6.4 64bit [GCC 7.2.0]
IPython6.2.1
OSLinux 4.13.0 45 generic x86_64 with debian stretch sid
myhdl0.10
myhdlpeek0.0.6
numpy1.13.3
pandas0.21.1
matplotlib2.1.1
sympy1.1.1
randomThe 'random' distribution was not found and is required by the application
Fri Jun 29 23:35:01 2018 MDT

In [2]:
#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

Compression of Number System Values


In [3]:
ConversionTable=pd.DataFrame()
ConversionTable['Decimal']=np.arange(0, 21)
ConversionTable['Binary']=[bin(i, 3) for i in np.arange(0, 21)]
ConversionTable['hex']=[hex(i) for i in np.arange(0, 21)]
ConversionTable['oct']=[oct(i) for i in np.arange(0, 21)]
ConversionTable


Out[3]:
Decimal Binary hex oct
0 0 000 0x0 0o0
1 1 001 0x1 0o1
2 2 010 0x2 0o2
3 3 011 0x3 0o3
4 4 100 0x4 0o4
5 5 101 0x5 0o5
6 6 110 0x6 0o6
7 7 111 0x7 0o7
8 8 1000 0x8 0o10
9 9 1001 0x9 0o11
10 10 1010 0xa 0o12
11 11 1011 0xb 0o13
12 12 1100 0xc 0o14
13 13 1101 0xd 0o15
14 14 1110 0xe 0o16
15 15 1111 0xf 0o17
16 16 10000 0x10 0o20
17 17 10001 0x11 0o21
18 18 10010 0x12 0o22
19 19 10011 0x13 0o23
20 20 10100 0x14 0o24

In [4]:
binarySum=lambda a, b, bits=2: np.binary_repr(a+b, bits)

In [5]:
for i in [[0,0], [0,1], [1,0], [1,1]]:
    print(f'{i[0]} + {i[1]} yields  {binarySum(*i)}_2, {int(binarySum(*i), 2)}_10')


0 + 0 yields  00_2, 0_10
0 + 1 yields  01_2, 1_10
1 + 0 yields  01_2, 1_10
1 + 1 yields  10_2, 2_10

Notice that when we add $1_2+1_2$ we get instead of just a single bit we also get a new bit in the next binary column which we call the carry bit. This yields the following truth table for what is called the Two Bit or Half adder

Half Adder


In [6]:
TwoBitAdderTT=pd.DataFrame()
TwoBitAdderTT['x2']=[0,0,1,1]
TwoBitAdderTT['x1']=[0,1,0,1]
TwoBitAdderTT['Sum']=[0,1,1,0]
TwoBitAdderTT['Carry']=[0,0,0,1]
TwoBitAdderTT


Out[6]:
x2 x1 Sum Carry
0 0 0 0 0
1 0 1 1 0
2 1 0 1 0
3 1 1 0 1

Looking at this truth table we can surmise the following


In [7]:
x1, x2=symbols('x_1, x_2')
Sum, Carry=symbols(r'\text{Sum}, \text{Carry}')
HASumDef=x1^x2; HACarryDef=x1 & x2
HASumEq=Eq(Sum, HASumDef); HACarryDef=Eq(Carry, HACarryDef)
HASumEq, HACarryDef


Out[7]:
$$\left ( \text{Sum} = x_{1} \veebar x_{2}, \quad \text{Carry} = x_{1} \wedge x_{2}\right )$$

We can thus generate the following myHDL


In [8]:
@block
def HalfAdder(x1, x2, Sum, Carry):
    """
    Half adder in myHDL
    I/O:
        x1 (bool): x1 input
        x2 (bool): x2 input
        Sum (bool): the sum Half Adder ouput
        Carry (bool): the carry Half Adder ouput
    """
    @always_comb
    def logic():
        Sum.next=x1^x2
        Carry.next=x1 & x2
    return logic

In [9]:
Peeker.clear()
x1=Signal(bool(0)); Peeker(x1, 'x1')
x2=Signal(bool(0)); Peeker(x2, 'x2')
Sum=Signal(bool(0)); Peeker(Sum, 'Sum')
Carry=Signal(bool(0)); Peeker(Carry, 'Carry')

DUT=HalfAdder(x1, x2, Sum, Carry)

def HalfAdder_TB():
    """
    Half Adder Testbench for use in python only
    """
    @instance
    def Stimules():
        for _, row in TwoBitAdderTT.iterrows():
            x1.next=row['x1']
            x2.next=row['x2']
            yield delay(1)
        raise StopSimulation()
    
    return instances()

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

In [10]:
Peeker.to_wavedrom('x2 x1 | Sum Carry', title='Half Adder Wave Form', tock=True)



In [11]:
HARes=Peeker.to_dataframe()
HARes=HARes[['x2', 'x1','Sum', 'Carry']]
HARes


Out[11]:
x2 x1 Sum Carry
0 0 0 0 0
1 0 1 1 0
2 1 0 1 0
3 1 1 0 1

Wich, we can then confirm via these results to the expected results via


In [12]:
TwoBitAdderTT==HARes


Out[12]:
x2 x1 Sum Carry
0 True True True True
1 True True True True
2 True True True True
3 True True True True

In [13]:
DUT.convert()
VerilogTextReader('HalfAdder');


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

 // File: HalfAdder.v
// Generated by MyHDL 0.10
// Date: Fri Jun 29 23:13:48 2018


`timescale 1ns/10ps

module HalfAdder (
    x1,
    x2,
    Sum,
    Carry
);
// Half adder in myHDL
// I/O:
//     x1 (bool): x1 input
//     x2 (bool): x2 input
//     Sum (bool): the sum Half Adder ouput
//     Carry (bool): the carry Half Adder ouput

input x1;
input x2;
output Sum;
wire Sum;
output Carry;
wire Carry;





assign Sum = (x1 ^ x2);
assign Carry = (x1 & x2);

endmodule

HalfAdder RTL HalfAdder Synthesis


In [26]:
@block
def HalfAdder_TBV():
    """
    Half Adder Testbench for use in Verilog
    """
    x1 = Signal(bool(0))
    x2 = Signal(bool(0))
    Sum = Signal(bool(0))
    Carry = Signal(bool(0))

    DUT = HalfAdder(x1, x2, Sum, Carry)

    testx1=Signal(intbv(int("".join([str(i) for i in TwoBitAdderTT['x1'].tolist()]), 2))[4:])
    testx2=Signal(intbv(int("".join([str(i) for i in TwoBitAdderTT['x2'].tolist()]), 2))[4:])

    
    @instance
    def Stimulus():
        for i in range(len(testx1)):
            x1.next = testx1[i]
            x2.next = testx2[i]
            yield delay(1)
            
        raise StopSimulation()
    
    @always_comb
    def print_data():
        print(x1, x2, Sum, Carry)
    
    return instances()


# create instaince of TB
TB = HalfAdder_TBV()
# convert to verilog with reintilzed values
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: Fri Jun 29 23:16:26 2018


`timescale 1ns/10ps

module HalfAdder_TBV (

);
// Half Adder Testbench for use in Verilog


wire Sum;
reg x1 = 0;
reg x2 = 0;
wire Carry;
wire [3:0] testx1;
wire [3:0] testx2;

assign testx1 = 4'd5;
assign testx2 = 4'd3;



assign Sum = (x1 ^ x2);
assign Carry = (x1 & x2);


initial begin: HALFADDER_TBV_STIMULUS
    integer i;
    for (i=0; i<4; i=i+1) begin
        x1 <= testx1[i];
        x2 <= testx2[i];
        # 1;
    end
    $finish;
end


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

endmodule

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

Where the above warning can be ignored since we are using testx1 and testx2 as stores for the binay set of inputs to apply to x1 and x2

The full adder

If we assume that a carry is an input we can then extend the truth table for the two-bit adder to a three-bit adder yielding


In [17]:
ThreeBitAdderTT=pd.DataFrame()
ThreeBitAdderTT['c1']=[0,0,0,0,1,1,1,1]
ThreeBitAdderTT['x2']=[0,0,1,1,0,0,1,1]
ThreeBitAdderTT['x1']=[0,1,0,1,0,1,0,1]
ThreeBitAdderTT['Sum']=[0,1,1,0,1,0,0,1]
ThreeBitAdderTT['Carry']=[0,0,0,1,0,1,1,1]
ThreeBitAdderTT


Out[17]:
c1 x2 x1 Sum Carry
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

! need a way to make K-Maps in python

whereupon making and reducing the maps from the three-bit adder truth table we get the following equations


In [18]:
c1, x1, x2=symbols('c_1,x_1, x_2')
Sum, Carry=symbols(r'\text{Sum}, \text{Carry}')
FASumDef=x1^x2^c1; FACarryDef=x1&x2 | x1&c1 | x2&c1
FASumEq=Eq(Sum, FASumDef); FACarryDef=Eq(Carry, FACarryDef)
FASumEq, FACarryDef


Out[18]:
$$\left ( \text{Sum} = c_{1} \veebar x_{1} \veebar x_{2}, \quad \text{Carry} = \left(c_{1} \wedge x_{1}\right) \vee \left(c_{1} \wedge x_{2}\right) \vee \left(x_{1} \wedge x_{2}\right)\right )$$

yielding the following next myHDL module


In [19]:
@block
def FullAdder(x1, x2, c1, Sum, Carry):
    """
    Full adder in myHDL
    I/O:
        x1 (bool): x1 input
        x2 (bool): x2 input
        c1 (bool): carry input
        Sum (bool): the sum Full Adder ouput
        Carry (bool): the carry Full Adder ouput
    
    Note!:
        There something wrong on the HDL side at the moment
    """
    
    @always_comb
    def logic():
        Sum.next=x1 ^ x2 ^c1
        Carry.next=(x1 & x2) | (x1 & c1) | (x2 & c1)
    return logic

In [21]:
Peeker.clear()
x1=Signal(bool(0)); Peeker(x1, 'x1')
x2=Signal(bool(0)); Peeker(x2, 'x2')
c1=Signal(bool(0)); Peeker(c1, 'c1')
Sum=Signal(bool(0)); Peeker(Sum, 'Sum')
Carry=Signal(bool(0)); Peeker(Carry, 'Carry')

DUT=FullAdder(x1, x2, c1, Sum, Carry)

def FullAdder_TB():
    
    @instance
    def Stimules():
        for _, row in ThreeBitAdderTT.iterrows():
            x1.next=row['x1']
            x2.next=row['x2']
            c1.next=row['c1']
            yield delay(1)
        raise StopSimulation()
    
    return instances()

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


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-b7b06c5590b9> in <module>()
     21     return instances()
     22 
---> 23 sim = Simulation(DUT, HalfAdder_TB(), *Peeker.instances()).run()

~/anaconda3/lib/python3.6/site-packages/myhdl/_Simulation.py in run(self, duration, quiet)
    150                     waiter = _pop()
    151                     try:
--> 152                         waiter.next(waiters, actives, exc)
    153                     except StopIteration:
    154                         continue

~/anaconda3/lib/python3.6/site-packages/myhdl/_Waiter.py in next(self, waiters, actives, exc)
    185         if self.hasRun:
    186             raise StopIteration
--> 187         clauses = next(self.generator)
    188         self.hasRun = 1
    189         clone = _SignalTupleWaiter(self.generator)

~/anaconda3/lib/python3.6/site-packages/myhdl/_always_comb.py in genfunc(self)
     81         func = self.func
     82         while 1:
---> 83             func()
     84             yield senslist

<ipython-input-19-9d778a4a99bd> in logic()
     13     @always_comb
     14     def logic():
---> 15         Sum.next=x1 ^ x2 ^c1
     16         Carry.next=(x1 & x2) | (x1 & c1) | (x2 & c1)
     17     return logic

~/anaconda3/lib/python3.6/site-packages/myhdl/_Signal.py in __getitem__(self, key)
    363 
    364     def __getitem__(self, key):
--> 365         return self._val[key]
    366 
    367     # integer-like methods

TypeError: 'bool' object is not subscriptable

In [22]:
Peeker.to_wavedrom('c1 x2 x1 | Sum Carry', title='Full Adder Wave Form', tock=True)



In [23]:
FARes=Peeker.to_dataframe()
FARes=FARes[['c1, x2','x1','Sum', 'Carry']]
FARes


---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-23-4de6aa39bde7> in <module>()
      1 FARes=Peeker.to_dataframe()
----> 2 FARes=FARes[['c1, x2','x1','Sum', 'Carry']]
      3 FARes

~/anaconda3/lib/python3.6/site-packages/pandas/core/frame.py in __getitem__(self, key)
   2131         if isinstance(key, (Series, np.ndarray, Index, list)):
   2132             # either boolean or fancy integer index
-> 2133             return self._getitem_array(key)
   2134         elif isinstance(key, DataFrame):
   2135             return self._getitem_frame(key)

~/anaconda3/lib/python3.6/site-packages/pandas/core/frame.py in _getitem_array(self, key)
   2175             return self._take(indexer, axis=0, convert=False)
   2176         else:
-> 2177             indexer = self.loc._convert_to_indexer(key, axis=1)
   2178             return self._take(indexer, axis=1, convert=True)
   2179 

~/anaconda3/lib/python3.6/site-packages/pandas/core/indexing.py in _convert_to_indexer(self, obj, axis, is_setter)
   1267                 if mask.any():
   1268                     raise KeyError('{mask} not in index'
-> 1269                                    .format(mask=objarr[mask]))
   1270 
   1271                 return _values_from_object(indexer)

KeyError: "['c1, x2'] not in index"

In [24]:
ThreeBitAdderTT==FARes


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-24-4311f575f82d> in <module>()
----> 1 ThreeBitAdderTT==FARes

~/anaconda3/lib/python3.6/site-packages/pandas/core/ops.py in f(self, other)
   1324     def f(self, other):
   1325         if isinstance(other, ABCDataFrame):  # Another DataFrame
-> 1326             return self._compare_frame(other, func, str_rep)
   1327         elif isinstance(other, ABCSeries):
   1328             return self._combine_series_infer(other, func, try_cast=False)

~/anaconda3/lib/python3.6/site-packages/pandas/core/frame.py in _compare_frame(self, other, func, str_rep, try_cast)
   4015     def _compare_frame(self, other, func, str_rep, try_cast=True):
   4016         if not self._indexed_same(other):
-> 4017             raise ValueError('Can only compare identically-labeled '
   4018                              'DataFrame objects')
   4019         return self._compare_frame_evaluate(other, func, str_rep,

ValueError: Can only compare identically-labeled DataFrame objects

In [25]:
DUT.convert()
VerilogTextReader('FullAdder');


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

 // File: FullAdder.v
// Generated by MyHDL 0.10
// Date: Fri Jun 29 23:15:35 2018


`timescale 1ns/10ps

module FullAdder (
    x1,
    x2,
    c1,
    Sum,
    Carry
);
// Full adder in myHDL
// I/O:
//     x1 (bool): x1 input
//     x2 (bool): x2 input
//     c1 (bool): carry input
//     Sum (bool): the sum Full Adder ouput
//     Carry (bool): the carry Full Adder ouput

input x1;
input x2;
input c1;
output Sum;
wire Sum;
output Carry;
wire Carry;





assign Sum = ((x1 ^ x2) ^ c1);
assign Carry = (((x1 & x2) | (x1 & c1)) | (x2 & c1));

endmodule

FullAdder RTL FullAdder Synthesis


In [27]:
@block
def FullAdder_TBV():
    """
    Full Adder Testbench for use in Verilog
    """
    x1=Signal(bool(0))
    x2=Signal(bool(0))
    c1=Signal(bool(0))
    Sum=Signal(bool(0))
    Carry=Signal(bool(0))
    
    DUT = FullAdder(x1, x2, c1, Sum, Carry)

    testx1=Signal(intbv(int("".join([str(i) for i in ThreeBitAdderTT['x1'].tolist()]), 2))[4:])
    testx2=Signal(intbv(int("".join([str(i) for i in ThreeBitAdderTT['x2'].tolist()]), 2))[4:])
    testc1=Signal(intbv(int("".join([str(i) for i in ThreeBitAdderTT['c1'].tolist()]), 2))[4:])

    
    
    @instance
    def Stimulus():
        for i in range(len(testx1)):
            x1.next=testx1[i]
            x2.next=testx2[i]
            c1.next=testc1[i]
            yield delay(1)
            
        raise StopSimulation()
    
    @always_comb
    def print_data():
        print(x1, x2, c1, Sum, Carry)
    
    return instances()


# create instaince of TB
TB = FullAdder_TBV()
# convert to verilog with reintilzed values
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: Fri Jun 29 23:24:09 2018


`timescale 1ns/10ps

module FullAdder_TBV (

);
// Full Adder Testbench for use in Verilog


wire Sum;
reg x1 = 0;
reg x2 = 0;
wire Carry;
reg c1 = 0;
wire [3:0] testx1;
wire [3:0] testx2;
wire [3:0] testc1;

assign testx1 = 4'd5;
assign testx2 = 4'd3;
assign testc1 = 4'd15;



assign Sum = ((x1 ^ x2) ^ c1);
assign Carry = (((x1 & x2) | (x1 & c1)) | (x2 & c1));


initial begin: FULLADDER_TBV_STIMULUS
    integer i;
    for (i=0; i<4; i=i+1) begin
        x1 <= testx1[i];
        x2 <= testx2[i];
        c1 <= testc1[i];
        # 1;
    end
    $finish;
end


always @(Carry, x1, c1, x2, Sum) begin: FULLADDER_TBV_PRINT_DATA
    $write("%h", x1);
    $write(" ");
    $write("%h", x2);
    $write(" ");
    $write("%h", c1);
    $write(" ");
    $write("%h", Sum);
    $write(" ");
    $write("%h", Carry);
    $write("\n");
end

endmodule

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

Where the above warning can be ignored since we are using testx1, testx2, and testc1 as stores for the binay set of inputs to apply to x1, x2, and c1


In [ ]: