Source code for hwtBuildsystem.hwt.multiConfigUnit

from copy import copy
from typing import List, Dict, Tuple, Union

from hdlConvertorAst.hdlAst import HdlIdDef, HdlValueId, HdlStmIf, \
    HdlStmBlock, HdlModuleDef, HdlCompInst
from hwt.code import And
from hwt.doc_markers import internal
from hwt.hdl.portItem import HdlPortItem
from hwt.hdl.types.defs import BIT, INT
from hwt.hdl.types.hdlType import HdlType
from hwt.hdl.value import HValue
from hwt.pyUtils.uniqList import UniqList
from hwt.serializer.mode import paramsToValTuple
from hwt.synthesizer.dummyPlatform import DummyPlatform
from hwt.synthesizer.hObjList import HObjList
from hwt.synthesizer.param import Param
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwt.synthesizer.unit import Unit
from hwt.synthesizer.utils import synthesised
from ipCorePackager.constants import INTF_DIRECTION, DIRECTION


[docs]def reduce_ternary(cond_val_pairs: List[Tuple[Union[HValue, RtlSignal], Union[HValue, RtlSignal]]], default: Union[HValue, RtlSignal]): """ .. code-block:: python reduce_ternary([(c0, v0), (c1, v1)], v3) # to v0 if c0 else v1 if c1 else v3 """ res = default for cond, val in reversed(cond_val_pairs): res = cond._ternary(val, res) return res
[docs]class MultiConfigUnitWrapper(Unit): """ Class which creates wrapper around multiple unit instances, the implementation is chosen based on generic/parameter values in HDL :attention: This is meant to be used for top component only, because it is useless for hwt design and it is useful only for integration of statically build component in to VHDL/Verilog """ def __init__(self, possible_variants: List[Unit]): assert possible_variants self._possible_variants = possible_variants super(MultiConfigUnitWrapper, self).__init__() def _copyParamsAndInterfaces(self): # note that the parameters are not added to HdlModuleDef (VHDL entity, Verilog module header) # as it was already build for p in self._possible_variants[0]._params: myP = Param(p.get_value()) self._registerParameter(p._name, myP) myP.set_value(p.get_value()) ns = self._store_manager.name_scope for p in sorted(self._params, key=lambda x: x._name): hdl_val = p.get_hdl_value() v = HdlIdDef() v.origin = p v.name = p.hdl_name = ns.checked_name(p._name, p) v.type = hdl_val._dtype v.value = hdl_val self._ctx.ent.params.append(v) for intf in self.possible_variants[0]._interfaces: # clone interface myIntf = copy(intf) if hasattr(myIntf, "_dtype"): myIntf._dtype = copy(myIntf._dtype) # sub-interfaces are not instantiated yet # myIntf._direction = intf._direction myIntf._direction = INTF_DIRECTION.opposite(intf._direction) self._registerInterface(intf._name, myIntf) object.__setattr__(self, intf._name, myIntf) ei = self._ctx.interfaces for i in self._interfaces: self._loadInterface(i, True) assert i._isExtern i._signalsForInterface(self._ctx, ei, self._store_manager.name_scope, reverse_dir=True) def _getDefaultName(self): return self._possible_variants[0]._getDefaultName() def _get_hdl_doc(self): return self._possible_variants[0]._get_hdl_doc() def _checkCompInstances(self): pass def _collectPortTypeVariants(self) -> List[Tuple[HdlPortItem, Dict[Tuple[Param, HValue], List[HdlType]]]]: res = [] param_variants = [paramsToValTuple(u) for u in self._units] for parent_port, port_variants in zip(self._ctx.ent.ports, zip(*(u._ctx.ent.ports for u in self._units))): param_val_to_t = {} for port_variant, params in zip(port_variants, param_variants): assert port_variant.name == parent_port.name, (port_variant.name, parent_port.name) t = port_variant._dtype assert len(params) == len(self._params), (params, self._params) params = params._asdict() for p in self._params: p_val = params[p.hdl_name] types = param_val_to_t.setdefault((p, p_val), UniqList()) types.append(t) res.append((parent_port, param_val_to_t)) return res def _injectParametersIntoPortTypes(self, port_type_variants: List[Tuple[HdlPortItem, Dict[Tuple[Param, HValue], List[HdlType]]]], param_signals: List[RtlSignal]): updated_type_ids = set() param_sig_by_name = {p.name: p for p in param_signals} param_value_domain = {} for parent_port, param_val_to_t in port_type_variants: for (param, param_value), port_types in param_val_to_t.items(): param_value_domain.setdefault(param, set()).add(param_value) for parent_port, param_val_to_t in port_type_variants: if id(parent_port._dtype) in updated_type_ids: continue # check which unique parameter values affects the type of the port # if the type changes with any parameter value integrate it in to type of the port # print(parent_port, param_val_to_t) type_to_param_values = {} for (param, param_value), port_types in param_val_to_t.items(): for pt in port_types: cond = type_to_param_values.setdefault(pt, UniqList()) cond.append((param, param_value)) assert type_to_param_values, parent_port if len(type_to_param_values) == 1: continue # type does not change # Param: values params_used = {} for t, param_values in type_to_param_values.items(): for (param, param_val) in param_values: params_used.setdefault(param, set()).add(param_val) # filter out parameters which are not part of type specification process for p, p_vals in list(params_used.items()): if len(param_value_domain[p]) == len(p_vals): params_used.pop(p) # reset sets used to check parameter values for p, p_vals in params_used.items(): p_vals.clear() if not params_used: raise AssertionError(parent_port, "Type changes between the variants but it does not depend on parameter", param_val_to_t) if len(params_used) == 1 and list(params_used.keys())[0].get_hdl_type() == INT: # try to extract param * x + y p_val_to_port_w = {} for t, param_values in type_to_param_values.items(): for (param, param_val) in param_values: if param not in params_used: continue assert param_val not in p_val_to_port_w or p_val_to_port_w[param_val] == t.bit_length(), parent_port p_val_to_port_w[param_val] = t.bit_length() # t_width = n*p + c _p_val_to_port_w = sorted(p_val_to_port_w.items()) t_width0, p0 = _p_val_to_port_w[0] t_width1, p1 = _p_val_to_port_w[1] # 0 == t_width0 - n*p0 + c # 0 == t_width1 - n*p1 + c # 0 == t_width0 - n*p0 - c + t_width1 - n*p1 - c # 0 == t_width0 + t_width1 - n*(p0 + p1) - 2c # c == (t_width0 + t_width1 - n*(p0 + p1) ) //2 # n has to be int, 0 < n <= t_width0/p0 # n is something like base size of port which is multiplied by parameter # we searching n for which we can resolve c found_nc = None for n in range(1, t_width0 // p0 + 1): c = (t_width0 + t_width1 - n * (p0 + p1)) // 2 if t_width0 - n * p0 + c == 0 and t_width1 - n * p1 + c == 0: found_nc = (n, c) break if found_nc is None: raise NotImplementedError() else: p = list(params_used.keys())[0] p = param_sig_by_name[p._name] (n, c) = found_nc t = parent_port._dtype t._bit_length = INT.from_py(n) * p + c t._bit_length._const = True updated_type_ids.add(id(t)) else: condition_and_type_width = [] default_width = None for t, p_vals in sorted(type_to_param_values.items(), key=lambda x: x[0].bit_length()): cond = And( *(param_sig_by_name[p.hdl_name]._eq(p_val) for p, p_val in p_vals if p in params_used) ) w = t.bit_length() if default_width is None: default_width = w condition_and_type_width.append((cond, w)) t = parent_port._dtype t._bit_length = reduce_ternary(condition_and_type_width, default_width) t._bit_length._const = True updated_type_ids.add(id(t))
[docs] def create_HdlModuleDef(self, target_platform: DummyPlatform, store_manager: "StoreManager"): ctx = self._ctx mdef = HdlModuleDef() mdef.dec = ctx.ent mdef.module_name = HdlValueId(ctx.ent.name, obj=ctx.ent) mdef.name = "rtl" # constant signals which represents the param/generic values param_signals = [ ctx.sig(p.hdl_name, p.get_hdl_type(), def_val=p.get_hdl_value()) for p in sorted(self._params, key=lambda x: x.hdl_name) ] # rewrite ports to use generic/params of this entity/module port_type_variants = self._collectPortTypeVariants() self._injectParametersIntoPortTypes(port_type_variants, param_signals) for p in param_signals: p._const = True p.hidden = False # instantiate component variants in if generate statement ns = store_manager.name_scope as_hdl_ast = self._store_manager.as_hdl_ast if_generate_cases = [] for u in self._units: # create instance ci = HdlCompInst() ci.origin = u ci.module_name = HdlValueId(u._ctx.ent.name, obj=u._ctx.ent) ci.name = HdlValueId(ns.checked_name(u._name + "_inst", ci), obj=u) e = u._ctx.ent ci.param_map.extend(e.params) # connect ports assert len(e.ports) == len(ctx.ent.ports) for p, parent_port in zip(e.ports, ctx.ent.ports): i = p.getInternSig() parent_port_sig = parent_port.getInternSig() assert i.name == parent_port_sig.name o = p.getOuterSig() # can not connect directly to parent port because type is different # but need to connect to something with the same name if o is p.src: p.src = p.dst else: assert o is p.dst, (o, p.dst) p.dst = p.src ci.port_map.append(p) # create if generate instantiation condition param_cmp_expr = BIT.from_py(1) assert len(u._params) == len(param_signals) for p, p_sig in zip(sorted(u._params, key=lambda x: x.hdl_name), param_signals): assert p.hdl_name == p_sig.name, (p.hdl_name, p_sig.name) param_cmp_expr = param_cmp_expr & p_sig._eq(p.get_hdl_value()) # add case if generate statement _param_cmp_expr = as_hdl_ast.as_hdl(param_cmp_expr) ci = as_hdl_ast.as_hdl_HdlCompInst(ci) b = HdlStmBlock() b.body.append(ci) b.in_preproc = True if_generate_cases.append((_param_cmp_expr, b)) if_generate = HdlStmIf() if_generate.in_preproc = True if_generate.labels.append(ns.checked_name("implementation_select", if_generate)) for c, ci in if_generate_cases: if if_generate.cond is None: if_generate.cond = c if_generate.if_true = ci else: if_generate.elifs.append((c, ci)) if_generate.if_false = store_manager.as_hdl_ast._static_assert_false( "The component was generated for this generic/params combination") mdef.objs.append(if_generate) for p in ctx.ent.ports: s = p.getInternSig() if p.direction != DIRECTION.IN: s.drivers.append(if_generate) else: s.endpoints.append(if_generate) ctx.arch = mdef return mdef
@internal def _to_rtl(self, target_platform:DummyPlatform, store_manager:"StoreManager", add_param_asserts=False): return Unit._to_rtl(self, target_platform, store_manager, add_param_asserts=add_param_asserts) def _impl(self): assert self._parent is None, "should be used only for top instances" self._ctx.create_HdlModuleDef = self.create_HdlModuleDef self.possible_variants = HObjList(self._possible_variants) self._copyParamsAndInterfaces()
if __name__ == "__main__": from hwtLib.examples.axi.simpleAxiRegs import SimpleAxiRegs from hwt.synthesizer.utils import to_rtl_str variants = [] for aw in [8, 16, 32]: u = SimpleAxiRegs() u.ADDR_WIDTH = aw u.DATA_WIDTH = 32 variants.append(u) u = MultiConfigUnitWrapper(variants) synthesised(u) #print(to_rtl_str(u)) #print(u._params)