Source code for hwtLib.examples.hierarchy.rippleadder

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from hwt.code import Concat
from hwt.hdl.types.bits import Bits
from hwt.interfaces.std import Signal, VectSignal
from hwt.serializer.mode import serializeParamsUniq
from hwt.synthesizer.hObjList import HObjList
from hwt.synthesizer.param import Param
from hwt.synthesizer.unit import Unit


[docs]@serializeParamsUniq class FullAdder(Unit): """ .. hwt-autodoc:: """ def _declr(self): self.a = Signal() self.b = Signal() self.ci = Signal() self.s = Signal()._m() self.co = Signal()._m() def _impl(self): # [note] it is usually better to copy commonly used self properties because it makes code shorter a, b, ci = self.a, self.b, self.ci self.s(a ^ b ^ ci) self.co(a & b | a & ci | b & ci)
# [wrong] the class is missing serializeParamsUniq decorator, # this means taht it will be serialized as a new module/entity for each instance, but it is not required
[docs]class RippleAdder0(Unit): """ .. hwt-autodoc:: """ def _config(self): # [possible improvement] you can specify the type directly, this may be more futureproof # in this case we could also control if the data type should be signed/unsigned self.p_wordlength = Param(4) def _declr(self): self.ci = Signal() self.a = VectSignal(self.p_wordlength) self.b = VectSignal(self.p_wordlength) self.s = VectSignal(self.p_wordlength)._m() self.co = Signal()._m() # [wrong] manually instantiated child components (it is better to use HObjList) self.fa0 = FullAdder() self.fa1 = FullAdder() self.fa2 = FullAdder() self.fa3 = FullAdder() def _impl(self): # [wrong] VectSignal is an Interface sub-class, it is ment to be used for IO of the component # it works but it has significant limitations, you should use self._sig() wihich handles name collisions # and has more confort API for clock/reset/default value specifications self.c = VectSignal(self.p_wordlength + 1) self.c[0](self.ci) self.co(self.c[self.p_wordlength]) # [wrong] manually unrolled/hardcoded for loop u_fa0 = self.fa0 u_fa1 = self.fa1 u_fa2 = self.fa2 u_fa3 = self.fa3 u_fa0.a(self.a[0]) u_fa1.a(self.a[1]) u_fa2.a(self.a[2]) u_fa3.a(self.a[3]) u_fa0.b(self.a[0]) u_fa1.b(self.a[1]) u_fa2.b(self.a[2]) u_fa3.b(self.a[3]) # [wrong] Why use bits of "c" singal if we can connect ports directly u_fa0.ci(self.c[0]) self.c[1](u_fa0.co) u_fa1.ci(self.c[1]) self.c[2](u_fa0.co) u_fa2.ci(self.c[2]) self.c[3](u_fa0.co) u_fa3.ci(self.c[3]) self.c[4](u_fa0.co) # [wrong] why to assing each bit separately if we can assing it all it once from concat of fa.s io self.s[0](u_fa0.s) self.s[1](u_fa1.s) self.s[2](u_fa2.s) self.s[3](u_fa3.s)
[docs]@serializeParamsUniq class RippleAdder1(Unit): """ .. hwt-autodoc:: """ def _config(self): self.p_wordlength = Param(4) def _declr(self): self.ci = Signal() self.a = VectSignal(self.p_wordlength) self.b = VectSignal(self.p_wordlength) self.s = VectSignal(self.p_wordlength)._m() self.co = Signal()._m() self.fa = HObjList([ FullAdder() for _ in range(self.p_wordlength) ]) def _impl(self): c = self._sig("c", Bits(self.p_wordlength + 1)) c[0](self.ci) for bitidx, fa in enumerate(self.fa): fa.a(self.a[bitidx]) fa.b(self.b[bitidx]) fa.ci(c[bitidx]) # not like in verilog, port is just another signal, direction of assignment does matter c[bitidx + 1](fa.co) self.s[bitidx](fa.s) self.co(c[self.p_wordlength])
[docs]@serializeParamsUniq class RippleAdder2(Unit): """ .. hwt-autodoc:: """ def _config(self): self.p_wordlength = Param(4) def _declr(self): self.ci = Signal() # [possible improvement] you can use io = lambda : VectSignal(self.p_wordlength) as macro # so you do not have to repeat same code self.a = VectSignal(self.p_wordlength) self.b = VectSignal(self.p_wordlength) self.s = VectSignal(self.p_wordlength)._m() self.co = Signal()._m() def _impl(self): # [wrong] it is useless to use an extra signal to connect ports, because it can be connected directly c = self._sig("c", Bits(self.p_wordlength + 1)) lci = [FullAdder() for _ in range(self.p_wordlength)] self.fa = HObjList(lci) c[0](self.ci) for bitIdx in range(self.p_wordlength): # [wrong] python iteration using range and indexing is slower than using enumerate() fa = lci[bitIdx] fa.a(self.a[bitIdx]) fa.b(self.b[bitIdx]) fa.ci(c[bitIdx]) # not like in verilog, port is just another signal, direction of assignment does matter c[bitIdx + 1](fa.co) self.s[bitIdx](fa.s) self.co(c[self.p_wordlength])
[docs]@serializeParamsUniq class RippleAdder3(Unit): """ .. hwt-autodoc:: """ def _config(self): self.p_wordlength = Param(4) def _declr(self): self.ci = Signal() self.a = VectSignal(self.p_wordlength) self.b = VectSignal(self.p_wordlength) self.s = VectSignal(self.p_wordlength)._m() self.co = Signal()._m() def _impl(self): carry = self.ci # [note] HObjList can be restered with or without items, however we need it in adwance because we # need registered FullAdder adder instances, because we need it's IO fa_list = self.fa = HObjList() for a, b in zip(self.a, self.b): # [note] componnets do not have to be declared in _declr(), but it is better # because the configuration of component can be still modified # after _declr() in _impl() the configuration of component is locked imediately after registration fa = FullAdder() # [note] the component have to be registered in order to spot the IO # the registration is done by assining to a property ot his object e.g. self.fa0 = fa # or by adding to some already registered object, in this case HObjList instance fa_list.append(fa) fa.a(a) fa.b(b) fa.ci(carry) carry = fa.co # [note] we have to reverse because of downto indexing self.s(Concat(*reversed([fa.s for fa in fa_list]))) self.co(carry)
if __name__ == "__main__": from hwt.synthesizer.utils import to_rtl_str u1 = RippleAdder1() u2 = RippleAdder2() u3 = RippleAdder3() from hwt.serializer.verilog import VerilogSerializer print(to_rtl_str(u1, serializer_cls=VerilogSerializer)) print(to_rtl_str(u2, serializer_cls=VerilogSerializer)) print(to_rtl_str(u3, serializer_cls=VerilogSerializer))