Task

"Simulate" a four-bit adder.

This design can be realized using four 1-bit full adders.
Each of these 1-bit full adders can be built with two half adders and an   or   gate. ;

Finally a half adder can be made using an   xor   gate and an   and   gate.

The   xor   gate can be made using two   nots,   two   ands   and one   or.

Not,   or   and   and,   the only allowed "gates" for the task, can be "imitated" by using the bitwise operators of your language.

If there is not a bit type in your language, to be sure that the   not   does not "invert" all the other bits of the basic type   (e.g. a byte)   we are not interested in,   you can use an extra   nand   (and   then   not)   with the constant   1   on one input.

Instead of optimizing and reducing the number of gates used for the final 4-bit adder,   build it in the most straightforward way,   connecting the other "constructive blocks",   in turn made of "simpler" and "smaller" ones.


Schematics of the "constructive blocks"


(Xor gate with ANDs, ORs and NOTs)        

   (A half adder)        

          (A full adder)            

                (A 4-bit adder)        












Solutions should try to be as descriptive as possible, making it as easy as possible to identify "connections" between higher-order "blocks".

It is not mandatory to replicate the syntax of higher-order blocks in the atomic "gate" blocks, i.e. basic "gate" operations can be performed as usual bitwise operations, or they can be "wrapped" in a block in order to expose the same syntax of higher-order blocks, at implementers' choice.

To test the implementation, show the sum of two four-bit numbers (in binary).




"""  
To run: 
    python3 Four_bit_adder_011.py
"""
 
from myhdl import *
 
#     define set of primitives 
 
@block
def NOTgate( a,  q ):   # define component name & interface
   """ q <- not(a) """
   @always_comb   # define asynchronous logic
   def NOTgateLogic():
      q.next = not a
 
   return NOTgateLogic   # return defined logic function, named 'NOTgate'
 
 
@block
def ANDgate( a, b,  q ):
   """ q <- a and b """
   @always_comb 
   def ANDgateLogic():
      q.next = a and b
 
   return ANDgateLogic
 
 
@block
def ORgate( a, b,  q ):
   """ q <- a or b """   
   @always_comb  
   def ORgateLogic():
      q.next = a or b
 
   return ORgateLogic
 
 
#     build components using defined primitive set
 
@block
def XORgate( a, b,  q ):
   """ q <- a xor b """   
   # define internal signals
   nota, notb, annotb, bnnota = [Signal(bool(0)) for i in range(4)]
   # name sub-components, and their interconnect 
   inv0 = NOTgate( a,  nota )
   inv1 = NOTgate( b,  notb )
   and2a = ANDgate( a, notb,  annotb )
   and2b = ANDgate( b, nota,  bnnota )
   or2a = ORgate( annotb, bnnota,  q )
 
   return inv0, inv1, and2a, and2b, or2a
 
 
@block
def HalfAdder( in_a, in_b,  summ, carry ):
   """ carry,sum is the sum of in_a, in_b """ 
   and2a =  ANDgate(in_a, in_b,  carry)
   xor2a =  XORgate(in_a, in_b,  summ)
 
   return and2a, xor2a
 
 
@block
def FullAdder( fa_c0, fa_a, fa_b,  fa_s, fa_c1 ):
   """ fa_c1,fa_s is the sum of fa_c0, fa_a, fa_b """
 
   ha1_s, ha1_c1, ha2_c1 = [Signal(bool(0)) for i in range(3)]
 
   HalfAdder01 = HalfAdder( fa_c0, fa_a,  ha1_s, ha1_c1 )
   HalfAdder02 = HalfAdder( ha1_s, fa_b,  fa_s,  ha2_c1 )
   or2a = ORgate(ha1_c1, ha2_c1,  fa_c1)
 
   return HalfAdder01, HalfAdder02, or2a
 
 
@block
def Adder4b( ina, inb,  cOut, sum4):
   ''' assemble 4 full adders ''' 
 
   cl = [Signal(bool()) for i in range(0,4)]  # carry signal list
   sl = [Signal(bool()) for i in range(4)]  # sum signal list
 
   HalfAdder0 = HalfAdder(        ina(0), inb(0),  sl[0], cl[1] )
   FullAdder1 = FullAdder( cl[1], ina(1), inb(1),  sl[1], cl[2] ) 
   FullAdder2 = FullAdder( cl[2], ina(2), inb(2),  sl[2], cl[3] ) 
   FullAdder3 = FullAdder( cl[3], ina(3), inb(3),  sl[3], cOut ) 
 
   sc = ConcatSignal(*reversed(sl))  # create internal bus for output list
 
   @always_comb
   def list2intbv():
      sum4.next = sc  # assign internal bus to actual output
 
   return HalfAdder0, FullAdder1, FullAdder2, FullAdder3, list2intbv
 
 
"""   define signals and code for testing
      -----------------------------------   """
t_co, t_s, t_a, t_b, dbug =  [Signal(bool(0)) for i in range(5)]
ina4, inb4, sum4 =  [Signal(intbv(0)[4:])  for i in range(3)]
 
from random import randrange 
 
@block
def Test_Adder4b():
   ''' Test Bench for Adder4b '''
   dut = Adder4b( ina4, inb4,  t_co, sum4 )
 
   @instance
   def check():
      print( "\n      b   a   |  c1    s   \n     -------------------" )
      for i in range(15):
         ina4.next, inb4.next = randrange(2**4), randrange(2**4)
         yield delay(5)
         print( "     %2d  %2d   |  %2d   %2d     " \
                % (ina4,inb4, t_co,sum4) )
         assert t_co * 16 + sum4 == ina4 + inb4  # test result
      print()
 
   return dut, check
 
 
"""   instantiate components and run test
      -----------------------------------   """
 
def main():
   simInst = Test_Adder4b()
   simInst.name = "mySimInst"
   simInst.config_sim(trace=True)  # waveform trace turned on
   simInst.run_sim(duration=None)
 
   inst = Adder4b( ina4, inb4,  t_co, sum4 )  #Multibit_Adder( a, b, s)
   inst.convert(hdl='VHDL')  # export VHDL
   inst.convert(hdl='Verilog')  # export Verilog
 
 
if __name__ == '__main__':
   main()
 