Source code for hwtBuildsystem.hwt.multiConfigHwModule

from copy import copy
from typing import 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.const import HConst
from hwt.pyUtils.setList import SetList
from hwt.serializer.mode import hwParamsToValTuple
from hwt.synthesizer.dummyPlatform import DummyPlatform
from hwt.hObjList import HObjList
from hwt.hwParam import HwParam
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwt.hwModule import HwModule
from ipCorePackager.constants import INTF_DIRECTION, DIRECTION


[docs] def reduce_ternary(cond_val_pairs: list[tuple[Union[HConst, RtlSignal], Union[HConst, RtlSignal]]], default: Union[HConst, 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 MultiConfigHwModuleWrapper(HwModule): """ 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[HwModule]): assert possible_variants self._possible_variants = possible_variants super(MultiConfigHwModuleWrapper, 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]._hwParams: myP = HwParam(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._hwParams, key=lambda x: x._name): hdl_val = p.get_hdl_value() v = HdlIdDef() v.origin = p v.name = p._name = ns.checked_name(p._name, p) v.type = hdl_val._dtype v.value = hdl_val self._rtlCtx.hwModDec.params.append(v) for hwIO in self.possible_variants[0]._hwIOs: # clone interface myHwIO = copy(hwIO) if hasattr(myHwIO, "_dtype"): myHwIO._dtype = copy(myHwIO._dtype) # sub-interfaces are not instantiated yet # myHwIO._direction = hwIO._direction myHwIO._direction = INTF_DIRECTION.opposite(hwIO._direction) self._registerHwIO(hwIO._name, myHwIO, hwIO._onParentPropertyPath, False) object.__setattr__(self, hwIO._name, myHwIO) ei = self._rtlCtx.hwIOs for hwIO in self._hwIOs: self._loadHwIODeclarations(hwIO, True) assert hwIO._isExtern hwIO._signalsForHwIO(self._rtlCtx, 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[HwParam, HConst], list[HdlType]]]]: res = [] param_variants = [hwParamsToValTuple(subMod) for subMod in self._subHwModules] for parent_port, port_variants in zip(self._rtlCtx.hwModDec.ports, zip(*(subMod._rtlCtx.hwModDec.ports for subMod in self._subHwModules))): 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._hwParams), (params, self._hwParams) params = params._asdict() for p in self._hwParams: p_val = params[p._name] types = param_val_to_t.setdefault((p, p_val), SetList()) types.append(t) res.append((parent_port, param_val_to_t)) return res def _injectParametersIntoPortTypes(self, port_type_variants: list[tuple[HdlPortItem, dict[tuple[HwParam, HConst], 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, SetList()) cond.append((param, param_value)) assert type_to_param_values, parent_port if len(type_to_param_values) == 1: continue # type does not change # HwParam: 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._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._rtlCtx mdef = HdlModuleDef() mdef.dec = ctx.hwModDec mdef.module_name = HdlValueId(ctx.hwModDec.name, obj=ctx.hwModDec) mdef.name = "rtl" # constant signals which represents the param/generic values param_signals = [ ctx.sig(p._name, p.get_hdl_type(), def_val=p.get_hdl_value()) for p in sorted(self._hwParams, key=lambda x: x._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._isUnnamedExpr = 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 subMod in self._subHwModules: # create instance ci = HdlCompInst() ci.origin = subMod ci.module_name = HdlValueId(subMod._rtlCtx.hwModDec.name, obj=subMod._rtlCtx.hwModDec) ci.name = HdlValueId(ns.checked_name(subMod._name + "_inst", ci), obj=subMod) e = subMod._rtlCtx.hwModDec ci.param_map.extend(e.params) # connect ports assert len(e.ports) == len(ctx.hwModDec.ports) for p, parent_port in zip(e.ports, ctx.hwModDec.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(subMod._hwParams) == len(param_signals) for p, p_sig in zip(sorted(subMod._hwParams, key=lambda x: x._name), param_signals): assert p._name == p_sig._name, (p._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.hwModDec.ports: s = p.getInternSig() if p.direction != DIRECTION.IN: s._rtlDrivers.append(if_generate) else: s._rtlEndpoints.append(if_generate) ctx.hwModDef = mdef return mdef
@internal def _to_rtl(self, target_platform:DummyPlatform, store_manager:"StoreManager", add_param_asserts=False): return HwModule._to_rtl(self, target_platform, store_manager, add_param_asserts=add_param_asserts)
[docs] def hwImpl(self): assert self._parent is None, "should be used only for top instances" self._rtlCtx.create_HdlModuleDef = self.create_HdlModuleDef self.possible_variants = HObjList(self._possible_variants) self._copyParamsAndInterfaces()
if __name__ == "__main__": # from hwt.synth import synthesised from hwtLib.examples.axi.simpleAxiRegs import SimpleAxiRegs from hwt.synth import to_rtl_str variants = [] for aw in [8, 16, 32]: m = SimpleAxiRegs() m.ADDR_WIDTH = aw m.DATA_WIDTH = 32 variants.append(m) m = MultiConfigHwModuleWrapper(variants) # synthesised(m) print(to_rtl_str(m)) #print(m._hwParams)