Exemple #1
0
class Input_Info(QWidget):
    """
    Create widget for displaying infos about filter specs and filter design method
    """
    sig_rx = pyqtSignal(object)  # incoming signals from input_tab_widgets
    sig_tx = pyqtSignal(object)
    from pyfda.libs.pyfda_qt_lib import emit

    def __init__(self, parent=None):
        super(Input_Info, self).__init__(parent)

        self.tab_label = 'Info'
        self.tool_tip = (
            "<span>Display the achieved filter specifications"
            " and more info about the filter design algorithm.</span>")

        self._construct_UI()
        self.load_dict()

    def process_sig_rx(self, dict_sig=None):
        """
        Process signals coming from sig_rx
        """
        # logger.debug("Processing {0}: {1}".format(type(dict_sig).__name__, dict_sig))
        if 'data_changed' in dict_sig or 'view_changed' in dict_sig\
                or 'specs_changed' in dict_sig:
            self.load_dict()

    def _construct_UI(self):
        """
        Intitialize the widget, consisting of:
        - Checkboxes for selecting the info to be displayed
        - A large text window for displaying infos about the filter design
          algorithm
        """
        bfont = QFont()
        bfont.setBold(True)

        # ============== UI Layout =====================================
        # widget / subwindow for filter infos
#        self.butFiltPerf = QToolButton("H(f)", self)
        self.butFiltPerf = QPushButton(self)
        self.butFiltPerf.setText("H(f)")
        self.butFiltPerf.setCheckable(True)
        self.butFiltPerf.setChecked(True)
        self.butFiltPerf.setToolTip("Display frequency response at test frequencies.")

        self.butDebug = QPushButton(self)
        self.butDebug.setText("Debug")
        self.butDebug.setCheckable(True)
        self.butDebug.setChecked(False)
        self.butDebug.setToolTip("Show debugging options.")

        self.butAbout = QPushButton("About", self)  # pop-up "About" window

        self.butSettings = QPushButton("Settings", self)  #
        self.butSettings.setCheckable(True)
        self.butSettings.setChecked(False)
        self.butSettings.setToolTip("Display and set some settings")

        layHControls1 = QHBoxLayout()
        layHControls1.addWidget(self.butFiltPerf)
        layHControls1.addWidget(self.butAbout)
        layHControls1.addWidget(self.butSettings)
        layHControls1.addWidget(self.butDebug)

        self.butDocstring = QPushButton("Doc$", self)
        self.butDocstring.setCheckable(True)
        self.butDocstring.setChecked(False)
        self.butDocstring.setToolTip("Display docstring from python filter method.")

        self.butRichText = QPushButton("RTF", self)
        self.butRichText.setCheckable(HAS_DOCUTILS)
        self.butRichText.setChecked(HAS_DOCUTILS)
        self.butRichText.setEnabled(HAS_DOCUTILS)
        self.butRichText.setToolTip("Render documentation in Rich Text Format.")

        self.butFiltDict = QPushButton("FiltDict", self)
        self.butFiltDict.setToolTip("Show filter dictionary for debugging.")
        self.butFiltDict.setCheckable(True)
        self.butFiltDict.setChecked(False)

        self.butFiltTree = QPushButton("FiltTree", self)
        self.butFiltTree.setToolTip("Show filter tree for debugging.")
        self.butFiltTree.setCheckable(True)
        self.butFiltTree.setChecked(False)

        layHControls2 = QHBoxLayout()
        layHControls2.addWidget(self.butDocstring)
        # layHControls2.addStretch(1)
        layHControls2.addWidget(self.butRichText)
        # layHControls2.addStretch(1)
        layHControls2.addWidget(self.butFiltDict)
        # layHControls2.addStretch(1)
        layHControls2.addWidget(self.butFiltTree)

        self.frmControls2 = QFrame(self)
        self.frmControls2.setLayout(layHControls2)
        self.frmControls2.setVisible(self.butDebug.isChecked())
        self.frmControls2.setContentsMargins(0, 0, 0, 0)

        lbl_settings_NFFT = QLabel(to_html("N_FFT =", frmt='bi'), self)
        self.led_settings_NFFT = QLineEdit(self)
        self.led_settings_NFFT.setText(str(params['N_FFT']))
        self.led_settings_NFFT.setToolTip("<span>Number of FFT points for frequency "
                                          "domain widgets.</span>")

        layGSettings = QGridLayout()
        layGSettings.addWidget(lbl_settings_NFFT, 1, 0)
        layGSettings.addWidget(self.led_settings_NFFT, 1, 1)

        self.frmSettings = QFrame(self)
        self.frmSettings.setLayout(layGSettings)
        self.frmSettings.setVisible(self.butSettings.isChecked())
        self.frmSettings.setContentsMargins(0, 0, 0, 0)

        layVControls = QVBoxLayout()
        layVControls.addLayout(layHControls1)
        layVControls.addWidget(self.frmControls2)
        layVControls.addWidget(self.frmSettings)

        self.frmMain = QFrame(self)
        self.frmMain.setLayout(layVControls)

        self.tblFiltPerf = QTableWidget(self)
        self.tblFiltPerf.setAlternatingRowColors(True)
#        self.tblFiltPerf.verticalHeader().setVisible(False)
        self.tblFiltPerf.horizontalHeader().setHighlightSections(False)
        self.tblFiltPerf.horizontalHeader().setFont(bfont)
        self.tblFiltPerf.verticalHeader().setHighlightSections(False)
        self.tblFiltPerf.verticalHeader().setFont(bfont)

        self.txtFiltInfoBox = QTextBrowser(self)
        self.txtFiltDict = QTextBrowser(self)
        self.txtFiltTree = QTextBrowser(self)

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmMain)

#        layVMain.addLayout(self.layHControls)
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(self.tblFiltPerf)
        splitter.addWidget(self.txtFiltInfoBox)
        splitter.addWidget(self.txtFiltDict)
        splitter.addWidget(self.txtFiltTree)
        # setSizes uses absolute pixel values, but can be "misused" by specifying values
        # that are way too large: in this case, the space is distributed according
        # to the _ratio_ of the values:
        splitter.setSizes([3000, 10000, 1000, 1000])
        layVMain.addWidget(splitter)

        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.butFiltPerf.clicked.connect(self._show_filt_perf)
        self.butAbout.clicked.connect(self._about_window)
        self.butSettings.clicked.connect(self._show_settings)
        self.led_settings_NFFT.editingFinished.connect(self._update_settings_nfft)
        self.butDebug.clicked.connect(self._show_debug)

        self.butFiltDict.clicked.connect(self._show_filt_dict)
        self.butFiltTree.clicked.connect(self._show_filt_tree)
        self.butDocstring.clicked.connect(self._show_doc)
        self.butRichText.clicked.connect(self._show_doc)

    def _about_window(self):
        self.about_widget = AboutWindow(self)  # important: Handle must be class attribute
        # self.opt_widget.show() # modeless dialog, i.e. non-blocking
        self.about_widget.exec_()  # modal dialog (blocking)

# ------------------------------------------------------------------------------
    def _show_debug(self):
        """
        Show / hide debug options depending on the state of the debug button
        """
        self.frmControls2.setVisible(self.butDebug.isChecked())

# ------------------------------------------------------------------------------
    def _show_settings(self):
        """
        Show / hide settings options depending on the state of the settings button
        """
        self.frmSettings.setVisible(self.butSettings.isChecked())

    def _update_settings_nfft(self):
        """ Update value for self.par1 from QLineEdit Widget"""
        params['N_FFT'] = safe_eval(self.led_settings_NFFT.text(), params['N_FFT'],
                                    sign='pos', return_type='int')
        self.led_settings_NFFT.setText(str(params['N_FFT']))
        self.emit({'data_changed': 'n_fft'})

# ------------------------------------------------------------------------------
    def load_dict(self):
        """
        update docs and filter performance
        """
        self._show_doc()
        self._show_filt_perf()
        self._show_filt_dict()
        self._show_filt_tree()

# ------------------------------------------------------------------------------
    def _show_doc(self):
        """
        Display info from filter design file and docstring
        """
        if hasattr(ff.fil_inst, 'info'):
            if self.butRichText.isChecked():
                self.txtFiltInfoBox.setText(publish_string(
                    self._clean_doc(ff.fil_inst.info), writer_name='html',
                    settings_overrides={'output_encoding': 'unicode'}))
            else:
                self.txtFiltInfoBox.setText(textwrap.dedent(ff.fil_inst.info))
        else:
            self.txtFiltInfoBox.setText("")

        if self.butDocstring.isChecked() and hasattr(ff.fil_inst, 'info_doc'):
            if self.butRichText.isChecked():
                self.txtFiltInfoBox.append(
                    '<hr /><b>Python module docstring:</b>\n')
                for doc in ff.fil_inst.info_doc:
                    self.txtFiltInfoBox.append(publish_string(
                     self._clean_doc(doc), writer_name='html',
                     settings_overrides={'output_encoding': 'unicode'}))
            else:
                self.txtFiltInfoBox.append('\nPython module docstring:\n')
                for doc in ff.fil_inst.info_doc:
                    self.txtFiltInfoBox.append(self._clean_doc(doc))

        self.txtFiltInfoBox.moveCursor(QTextCursor.Start)

    def _clean_doc(self, doc):
        """
        Remove uniform number of leading blanks from docstrings for subsequent
        processing of rich text. The first line is treated differently, _all_
        leading blanks are removed (if any). This allows for different formats
        of docstrings.
        """
        lines = doc.splitlines()
        result = lines[0].lstrip() + "\n" + textwrap.dedent("\n".join(lines[1:]))
        return result

# ------------------------------------------------------------------------------
    def _show_filt_perf(self):
        """
        Print filter properties in a table at frequencies of interest. When
        specs are violated, colour the table entry in red.
        """

        antiC = False

        def _find_min_max(self, f_start, f_stop, unit='dB'):
            """
            Find minimum and maximum magnitude and the corresponding frequencies
            for the filter defined in the filter dict in a given frequency band
            [f_start, f_stop].
            """
            w = np.linspace(f_start, f_stop, params['N_FFT'])*2*np.pi
            [w, H] = sig.freqz(bb, aa, worN=w)

            # add antiCausals if we have them
            if (antiC):
               #
               # Evaluate transfer function of anticausal half on the same freq grid.
               #
               wa, ha = sig.freqz(bbA, aaA, worN=w)
               ha = ha.conjugate()
               #
               # Total transfer function is the product
               #
               H = H*ha

            f = w / (2.0 * pi)  # frequency normalized to f_S
            H_abs = abs(H)
            H_max = max(H_abs)
            H_min = min(H_abs)
            F_max = f[np.argmax(H_abs)]  # find the frequency where H_abs
            F_min = f[np.argmin(H_abs)]  # becomes max resp. min
            if unit == 'dB':
                H_max = 20*log10(H_max)
                H_min = 20*log10(H_min)
            return F_min, H_min, F_max, H_max
        # ------------------------------------------------------------------

        self.tblFiltPerf.setVisible(self.butFiltPerf.isChecked())
        if self.butFiltPerf.isChecked():

            bb = fb.fil[0]['ba'][0]
            aa = fb.fil[0]['ba'][1]

            # 'rpk' means nonCausal filter
            if 'rpk' in fb.fil[0]:
                antiC = True
                bbA = fb.fil[0]['baA'][0]
                aaA = fb.fil[0]['baA'][1]
                bbA = bbA.conjugate()
                aaA = aaA.conjugate()

            f_S = fb.fil[0]['f_S']

            f_lbls = []
            f_vals = []
            a_lbls = []
            a_targs = []
            a_targs_dB = []
            a_test = []
            ft = fb.fil[0]['ft']  # get filter type ('IIR', 'FIR')
            unit = fb.fil[0]['amp_specs_unit']
            unit = 'dB'  # fix this for the moment
            # construct pairs of corner frequency and corresponding amplitude
            # labels in ascending frequency for each response type
            if fb.fil[0]['rt'] in {'LP', 'HP', 'BP', 'BS', 'HIL'}:
                if fb.fil[0]['rt'] == 'LP':
                    f_lbls = ['F_PB', 'F_SB']
                    a_lbls = ['A_PB', 'A_SB']
                elif fb.fil[0]['rt'] == 'HP':
                    f_lbls = ['F_SB', 'F_PB']
                    a_lbls = ['A_SB', 'A_PB']
                elif fb.fil[0]['rt'] == 'BP':
                    f_lbls = ['F_SB', 'F_PB', 'F_PB2', 'F_SB2']
                    a_lbls = ['A_SB', 'A_PB', 'A_PB', 'A_SB2']
                elif fb.fil[0]['rt'] == 'BS':
                    f_lbls = ['F_PB', 'F_SB', 'F_SB2', 'F_PB2']
                    a_lbls = ['A_PB', 'A_SB', 'A_SB', 'A_PB2']
                elif fb.fil[0]['rt'] == 'HIL':
                    f_lbls = ['F_PB', 'F_PB2']
                    a_lbls = ['A_PB', 'A_PB']

            # Try to get lists of frequency / amplitude specs from the filter dict
            # that correspond to the f_lbls / a_lbls pairs defined above
            # When one of the labels doesn't exist in the filter dict, delete
            # all corresponding amplitude and frequency entries.
                err = [False] * len(f_lbls)  # initialize error list
                f_vals = []
                a_targs = []
                for i in range(len(f_lbls)):
                    try:
                        f = fb.fil[0][f_lbls[i]]
                        f_vals.append(f)
                    except KeyError as e:
                        f_vals.append('')
                        err[i] = True
                        logger.debug(e)
                    try:
                        a = fb.fil[0][a_lbls[i]]
                        a_dB = lin2unit(fb.fil[0][a_lbls[i]], ft, a_lbls[i], unit)
                        a_targs.append(a)
                        a_targs_dB.append(a_dB)
                    except KeyError as e:
                        a_targs.append('')
                        a_targs_dB.append('')
                        err[i] = True
                        logger.debug(e)

                for i in range(len(f_lbls)):
                    if err[i]:
                        del f_lbls[i]
                        del f_vals[i]
                        del a_lbls[i]
                        del a_targs[i]
                        del a_targs_dB[i]

                f_vals = np.asarray(f_vals)  # convert to numpy array

                logger.debug("F_test_labels = %s" % f_lbls)

                # Calculate frequency response at test frequencies
                [w_test, a_test] = sig.freqz(bb, aa, 2.0 * pi * f_vals.astype(float))
                # add antiCausals if we have them
                if (antiC):
                   wa, ha = sig.freqz(bbA, aaA, 2.0 * pi * f_vals.astype(float))
                   ha = ha.conjugate()
                   a_test = a_test*ha

            (F_min, H_min, F_max, H_max) = _find_min_max(self, 0, 1, unit='V')
            # append frequencies and values for min. and max. filter reponse to
            # test vector

            f_lbls += ['Min.', 'Max.']
            # QTableView does not support direct formatting, use QLabel

            f_vals = np.append(f_vals, [F_min, F_max])
            a_targs = np.append(a_targs, [np.nan, np.nan])
            a_targs_dB = np.append(a_targs_dB, [np.nan, np.nan])
            a_test = np.append(a_test, [H_min, H_max])
            # calculate response of test frequencies in dB
            a_test_dB = -20*log10(abs(a_test))

            # get filter type ('IIR', 'FIR') for dB <-> lin conversion
            ft = fb.fil[0]['ft']
#            unit = fb.fil[0]['amp_specs_unit']
            unit = 'dB'  # make this fixed for the moment

            # build a list with the corresponding target specs:
            a_targs_pass = []
            eps = 1e-3
            for i in range(len(f_lbls)):
                if 'PB' in f_lbls[i]:
                    a_targs_pass.append((a_test_dB[i] - a_targs_dB[i]) < eps)
                    a_test[i] = 1 - abs(a_test[i])
                elif 'SB' in f_lbls[i]:
                    a_targs_pass.append(a_test_dB[i] >= a_targs_dB[i])
                else:
                    a_targs_pass.append(True)

            self.targs_spec_passed = np.all(a_targs_pass)

            logger.debug(
                "H_targ = {0}\n"
                "H_test = {1}\n"
                "H_test_dB = {2}\n"
                "F_test = {3}\n"
                "H_targ_pass = {4}\n"
                "passed: {5}\n".format(a_targs,  a_test,  a_test_dB, f_vals,
                                       a_targs_pass, self.targs_spec_passed))

            self.tblFiltPerf.setRowCount(len(a_test))  # number of table rows
            self.tblFiltPerf.setColumnCount(5)  # number of table columns

            self.tblFiltPerf.setHorizontalHeaderLabels([
                'f/{0:s}'.format(fb.fil[0]['freq_specs_unit']), 'Spec\n(dB)',
                '|H(f)|\n(dB)', 'Spec', '|H(f)|'])
            self.tblFiltPerf.setVerticalHeaderLabels(f_lbls)
            for row in range(len(a_test)):
                self.tblFiltPerf.setItem(
                    row, 0, QTableWidgetItem(str('{0:.4g}'.format(f_vals[row]*f_S))))
                self.tblFiltPerf.setItem(
                    row, 1, QTableWidgetItem(str('%2.3g'%(-a_targs_dB[row]))))
                self.tblFiltPerf.setItem(
                    row, 2, QTableWidgetItem(str('%2.3f'%(-a_test_dB[row]))))
                if a_targs[row] < 0.01:
                    self.tblFiltPerf.setItem(
                        row, 3, QTableWidgetItem(str('%.3e'%(a_targs[row]))))
                else:
                    self.tblFiltPerf.setItem(
                        row, 3, QTableWidgetItem(str('%2.4f'%(a_targs[row]))))
                if a_test[row] < 0.01:
                    self.tblFiltPerf.setItem(
                        row, 4, QTableWidgetItem(str('%.3e'%(abs(a_test[row])))))
                else:
                    self.tblFiltPerf.setItem(
                        row, 4, QTableWidgetItem(str('%.4f'%(abs(a_test[row])))))

                if not a_targs_pass[row]:
                    self.tblFiltPerf.item(row, 1).setBackground(QtGui.QColor('red'))
                    self.tblFiltPerf.item(row, 3).setBackground(QtGui.QColor('red'))

            self.tblFiltPerf.resizeColumnsToContents()
            self.tblFiltPerf.resizeRowsToContents()

# ------------------------------------------------------------------------------
    def _show_filt_dict(self):
        """
        Print filter dict for debugging
        """
        self.txtFiltDict.setVisible(self.butFiltDict.isChecked())

        fb_sorted = [str(key) + ' : ' + str(fb.fil[0][key])
                     for key in sorted(fb.fil[0].keys())]
        dictstr = pprint.pformat(fb_sorted)
#        dictstr = pprint.pformat(fb.fil[0])
        self.txtFiltDict.setText(dictstr)

# ------------------------------------------------------------------------------
    def _show_filt_tree(self):
        """
        Print filter tree for debugging
        """
        self.txtFiltTree.setVisible(self.butFiltTree.isChecked())

        ftree_sorted = ['<b>' + str(key) + ' : ' + '</b>' + str(fb.fil_tree[key])
                        for key in sorted(fb.fil_tree.keys())]
        dictstr = pprint.pformat(ftree_sorted, indent=4)
#        dictstr = pprint.pformat(fb.fil[0])
        self.txtFiltTree.setText(dictstr)
Exemple #2
0
class Input_Fixpoint_Specs(QWidget):
    """
    Create the widget that holds the dynamically loaded fixpoint filter ui
    """

    # sig_resize = pyqtSignal()  # emit a signal when the image has been resized
    sig_rx_local = pyqtSignal(object)  # incoming from subwidgets -> process_sig_rx_local
    sig_rx = pyqtSignal(object)  # incoming, connected to input_tab_widget.sig_rx
    sig_tx = pyqtSignal(object)  # outcgoing
    from pyfda.libs.pyfda_qt_lib import emit

    def __init__(self, parent=None):
        super(Input_Fixpoint_Specs, self).__init__(parent)

        self.tab_label = 'Fixpoint'
        self.tool_tip = ("<span>Select a fixpoint implementation for the filter,"
                         " simulate it or generate a Verilog netlist.</span>")
        self.parent = parent
        self.fx_path = os.path.realpath(
            os.path.join(dirs.INSTALL_DIR, 'fixpoint_widgets'))

        self.no_fx_filter_img = os.path.join(self.fx_path, "no_fx_filter.png")
        if not os.path.isfile(self.no_fx_filter_img):
            logger.error("Image {0:s} not found!".format(self.no_fx_filter_img))

        self.default_fx_img = os.path.join(self.fx_path, "default_fx_img.png")
        if not os.path.isfile(self.default_fx_img):
            logger.error("Image {0:s} not found!".format(self.default_fx_img))

        self._construct_UI()
        inst_wdg_list = self._update_filter_cmb()
        if len(inst_wdg_list) == 0:
            logger.warning("No fixpoint filter found for this type of filter!")
        else:
            logger.debug("Imported {0:d} fixpoint filters:\n{1}"
                         .format(len(inst_wdg_list.split("\n"))-1, inst_wdg_list))
        self._update_fixp_widget()

# ------------------------------------------------------------------------------
    def process_sig_rx_local(self, dict_sig: dict = None) -> None:
        """
        Process signals coming in from input and output quantizer subwidget and the
        dynamically instantiated subwidget and emit {'fx_sim': 'specs_changed'} in
        the end.
        """
        if dict_sig['id'] == id(self):
            logger.warning(f'RX_LOCAL - Stopped infinite loop: "{first_item(dict_sig)}"')
            return

        elif 'fx_sim' in dict_sig and dict_sig['fx_sim'] == 'specs_changed':
            self.wdg_dict2ui()  # update wordlengths in UI and set RUN button to 'changed'
            dict_sig.update({'id': id(self)})  # propagate 'specs_changed' with self 'id'
            self.emit(dict_sig)
            return

        # ---- Process input and output quantizer settings ('ui' in dict_sig) --
        elif 'ui' in dict_sig:
            if 'wdg_name' not in dict_sig:
                logger.warning(f"No key 'wdg_name' in dict_sig:\n{pprint_log(dict_sig)}")
                return

            elif dict_sig['wdg_name'] == 'w_input':
                """
                Input fixpoint format has been changed or butLock has been clicked.
                When I/O lock is active, copy input fixpoint word format to output
                word format.
                """
                if dict_sig['ui'] == 'butLock'\
                        and not self.wdg_w_input.butLock.isChecked():
                    # butLock was deactivitated, don't do anything
                    return
                elif self.wdg_w_input.butLock.isChecked():
                    # but lock was activated or wordlength setting have been changed
                    fb.fil[0]['fxqc']['QO']['WI'] = fb.fil[0]['fxqc']['QI']['WI']
                    fb.fil[0]['fxqc']['QO']['WF'] = fb.fil[0]['fxqc']['QI']['WF']
                    fb.fil[0]['fxqc']['QO']['W'] = fb.fil[0]['fxqc']['QI']['W']

            elif dict_sig['wdg_name'] == 'w_output':
                """
                Output fixpoint format has been changed. When I/O lock is active, copy
                output fixpoint word format to input word format.
                """
                if self.wdg_w_input.butLock.isChecked():
                    fb.fil[0]['fxqc']['QI']['WI'] = fb.fil[0]['fxqc']['QO']['WI']
                    fb.fil[0]['fxqc']['QI']['WF'] = fb.fil[0]['fxqc']['QO']['WF']
                    fb.fil[0]['fxqc']['QI']['W'] = fb.fil[0]['fxqc']['QO']['W']

            elif dict_sig['wdg_name'] in {'q_output', 'q_input'}:
                pass
            else:
                logger.error("Unknown wdg_name '{0}' in dict_sig:\n{1}"
                             .format(dict_sig['wdg_name'], pprint_log(dict_sig)))
                return

            if dict_sig['ui'] not in {'WI', 'WF', 'ovfl', 'quant', 'cmbW', 'butLock'}:
                logger.warning("Unknown value '{0}' for key 'ui'".format(dict_sig['ui']))

            self.wdg_dict2ui()  # update wordlengths in UI and set RUN button to 'changed'
            self.emit({'fx_sim': 'specs_changed'})  # propagate 'specs_changed'

        else:
            logger.error(f"Unknown key/value in 'dict_sig':\n{pprint_log(dict_sig)}")

# ------------------------------------------------------------------------------
    def process_sig_rx(self, dict_sig: dict = None) -> None:
        """
        Process signals coming in via `sig_rx` from other widgets.

        Trigger fx simulation:

        1. ``fx_sim': 'init'``: Start fixpoint simulation by sending
           'fx_sim':'start_fx_response_calculation'

        2. ``fx_sim_calc_response()``: Receive stimulus from widget in
            'fx_sim':'calc_frame_fx_response' and pass it to fixpoint simulation method

        3. Store fixpoint response in `fb.fx_result` and return to initiating routine
        """

        # logger.info(
        #     "SIG_RX(): vis={0}\n{1}".format(self.isVisible(), pprint_log(dict_sig)))
        # logger.debug(f'SIG_RX():  "{first_item(dict_sig)}"')

        if dict_sig['id'] == id(self):
            # logger.warning(f'Stopped infinite loop: "{first_item(dict_sig)}"')
            return

        elif 'data_changed' in dict_sig and dict_sig['data_changed'] == "filter_designed":
            # New filter has been designed, update list of available filter topologies
            self._update_filter_cmb()
            return

        elif 'data_changed' in dict_sig or\
             ('view_changed' in dict_sig and dict_sig['view_changed'] == 'q_coeff'):
            # Filter data has changed (but not the filter type) or the coefficient
            # format / wordlength have been changed in `input_coeffs`. The latter means
            # the view / display has been changed (wordlength) but not the actual
            # coefficients in the `input_coeffs` widget. However, the wordlength setting
            # is copied to the fxqc dict and from there to the fixpoint widget.
            # - update fields in the fixpoint filter widget - wordlength may have
            #   been changed.
            # - Set RUN button to "changed" in wdg_dict2ui()
            self.wdg_dict2ui()

        # --------------- FX Simulation -------------------------------------------
        elif 'fx_sim' in dict_sig:
            if dict_sig['fx_sim'] == 'init':
                # fixpoint simulation has been started externally, e.g. by
                # `impz.impz_init()`, return a handle to the fixpoint filter function
                # via signal-slot connection
                if not self.fx_wdg_found:
                    logger.error("No fixpoint widget found!")
                    qstyle_widget(self.butSimFx, "error")
                    self.emit({'fx_sim': 'error'})
                elif self.fx_sim_init() != 0:  # returned an error
                    qstyle_widget(self.butSimFx, "error")
                    self.emit({'fx_sim': 'error'})
                else:
                    self.emit({'fx_sim': 'start_fx_response_calculation',
                               'fxfilter_func': self.fx_filt_ui.fxfilter})

            elif dict_sig['fx_sim'] == 'calc_frame_fx_response':
                self.fx_sim_calc_response(dict_sig)
                # return to the routine collecting the response frame by frame
                return

            elif dict_sig['fx_sim'] == 'specs_changed':
                # fixpoint specification have been changed somewhere, update ui
                # and set run button to "changed" in wdg_dict2ui()
                self.wdg_dict2ui()
            elif dict_sig['fx_sim'] == 'finish':
                qstyle_widget(self.butSimFx, "normal")
            else:
                logger.error('Unknown "fx_sim" command option "{0}"\n'
                             '\treceived from "{1}".'
                             .format(dict_sig['fx_sim'], dict_sig['class']))

        # ---- resize image when "Fixpoint" tab is selected or widget size is changed:
        elif 'ui_changed' in dict_sig and dict_sig['ui_changed'] in {'resized', 'tab'}\
                and self.isVisible():
            self.resize_img()

# ------------------------------------------------------------------------------
    def _construct_UI(self) -> None:
        """
        Intitialize the main GUI, consisting of:

        - A combo box to select the filter topology and an image of the topology

        - The input quantizer

        - The UI of the fixpoint filter widget

        - Simulation and export buttons
        """
# ------------------------------------------------------------------------------
        # Define frame and layout for the dynamically updated filter widget
        # The actual filter widget is instantiated in self.set_fixp_widget() later on

        self.layH_fx_wdg = QHBoxLayout()
        # self.layH_fx_wdg.setContentsMargins(*params['wdg_margins'])
        frmHDL_wdg = QFrame(self)
        frmHDL_wdg.setLayout(self.layH_fx_wdg)
        # frmHDL_wdg.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

# ------------------------------------------------------------------------------
#       Initialize fixpoint filter combobox, title and description
# ------------------------------------------------------------------------------
        self.cmb_fx_wdg = QComboBox(self)
        self.cmb_fx_wdg.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.lblTitle = QLabel("not set", self)
        self.lblTitle.setWordWrap(True)
        self.lblTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        layHTitle = QHBoxLayout()
        layHTitle.addWidget(self.cmb_fx_wdg)
        layHTitle.addWidget(self.lblTitle)

        self.frmTitle = QFrame(self)
        self.frmTitle.setLayout(layHTitle)
        self.frmTitle.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Input and Output Quantizer
# ------------------------------------------------------------------------------
#       - instantiate widgets for input and output quantizer
#       - pass the quantization (sub-?) dictionary to the constructor
# ------------------------------------------------------------------------------

        self.wdg_w_input = UI_W(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='w_input', label='', lock_visible=True)
        self.wdg_w_input.sig_tx.connect(self.process_sig_rx_local)

        cmb_q = ['round', 'floor', 'fix']

        self.wdg_w_output = UI_W(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='w_output', label='')
        self.wdg_w_output.sig_tx.connect(self.process_sig_rx_local)

        self.wdg_q_output = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='q_output',
                                 label='Output Format <i>Q<sub>Y&nbsp;</sub></i>:',
                                 cmb_q=cmb_q, cmb_ov=['wrap', 'sat'])
        self.wdg_q_output.sig_tx.connect(self.sig_rx_local)

        if HAS_DS:
            cmb_q.append('dsm')
        self.wdg_q_input = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='q_input',
                                label='Input Format <i>Q<sub>X&nbsp;</sub></i>:',
                                cmb_q=cmb_q)
        self.wdg_q_input.sig_tx.connect(self.sig_rx_local)

        # Layout and frame for input quantization
        layVQiWdg = QVBoxLayout()
        layVQiWdg.addWidget(self.wdg_q_input)
        layVQiWdg.addWidget(self.wdg_w_input)
        frmQiWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQiWdg.setLayout(layVQiWdg)
        frmQiWdg.setContentsMargins(*params['wdg_margins'])

        # Layout and frame for output quantization
        layVQoWdg = QVBoxLayout()
        layVQoWdg.addWidget(self.wdg_q_output)
        layVQoWdg.addWidget(self.wdg_w_output)
        frmQoWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQoWdg.setLayout(layVQoWdg)
        frmQoWdg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Dynamically updated image of filter topology (label as placeholder)
# ------------------------------------------------------------------------------
        # allow setting background color
        # lbl_fixp_img_palette = QPalette()
        # lbl_fixp_img_palette.setColor(QPalette(window, Qt: white))
        # lbl_fixp_img_palette.setBrush(self.backgroundRole(), QColor(150, 0, 0))
        # lbl_fixp_img_palette.setColor(QPalette: WindowText, Qt: blue)

        self.lbl_fixp_img = QLabel("img not set", self)
        self.lbl_fixp_img.setAutoFillBackground(True)
        # self.lbl_fixp_img.setPalette(lbl_fixp_img_palette)
        # self.lbl_fixp_img.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.embed_fixp_img(self.no_fx_filter_img)
        layHImg = QHBoxLayout()
        layHImg.setContentsMargins(0, 0, 0, 0)
        layHImg.addWidget(self.lbl_fixp_img)  # , Qt.AlignCenter)
        self.frmImg = QFrame(self)
        self.frmImg.setLayout(layHImg)
        self.frmImg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Simulation and export Buttons
# ------------------------------------------------------------------------------
        self.butExportHDL = QPushButton(self)
        self.butExportHDL.setToolTip(
            "Create Verilog or VHDL netlist for fixpoint filter.")
        self.butExportHDL.setText("Create HDL")

        self.butSimFx = QPushButton(self)
        self.butSimFx.setToolTip("Start fixpoint simulation.")
        self.butSimFx.setText("Sim. FX")

        self.layHHdlBtns = QHBoxLayout()
        self.layHHdlBtns.addWidget(self.butSimFx)
        self.layHHdlBtns.addWidget(self.butExportHDL)
        # This frame encompasses the HDL buttons sim and convert
        frmHdlBtns = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmHdlBtns.setLayout(self.layHHdlBtns)
        frmHdlBtns.setContentsMargins(*params['wdg_margins'])

# -------------------------------------------------------------------
#       Top level layout
# -------------------------------------------------------------------
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(frmHDL_wdg)
        splitter.addWidget(frmQoWdg)
        splitter.addWidget(self.frmImg)

        # setSizes uses absolute pixel values, but can be "misused" by specifying values
        # that are way too large: in this case, the space is distributed according
        # to the _ratio_ of the values:
        splitter.setSizes([3000, 3000, 5000])

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmTitle)
        layVMain.addWidget(frmHdlBtns)
        layVMain.addWidget(frmQiWdg)
        layVMain.addWidget(splitter)
        layVMain.addStretch()
        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        self.sig_rx_local.connect(self.process_sig_rx_local)
        # dynamic connection in `self._update_fixp_widget()`:
        # -----
        # if hasattr(self.fx_filt_ui, "sig_rx"):
        #     self.sig_rx.connect(self.fx_filt_ui.sig_rx)
        # if hasattr(self.fx_filt_ui, "sig_tx"):
        #     self.fx_filt_ui.sig_tx.connect(self.sig_rx_local)
        # ----
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.cmb_fx_wdg.currentIndexChanged.connect(self._update_fixp_widget)
        self.butExportHDL.clicked.connect(self.exportHDL)
        self.butSimFx.clicked.connect(lambda x: self.emit({'fx_sim': 'start'}))
        # ----------------------------------------------------------------------
        # EVENT FILTER
        # ----------------------------------------------------------------------
        # # monitor events and generate sig_resize event when resized
        # self.lbl_fixp_img.installEventFilter(self)
        # # ... then redraw image when resized
        # self.sig_resize.connect(self.resize_img)

# ------------------------------------------------------------------------------
    def _update_filter_cmb(self) -> str:
        """
        (Re-)Read list of available fixpoint filters for a given filter design
        every time a new filter design is selected.

        Then try to import the fixpoint designs in the list and populate the
        fixpoint implementation combo box `self.cmb_fx_wdg` when successfull.

        Returns
        -------
        inst_wdg_str: str
          string with all fixpoint widgets that could be instantiated successfully
        """
        inst_wdg_str = ""  # full names of successfully instantiated widgets for logging
        # remember last fx widget setting:
        last_fx_wdg = qget_cmb_box(self.cmb_fx_wdg, data=False)
        self.cmb_fx_wdg.clear()
        fc = fb.fil[0]['fc']

        if 'fix' in fb.filter_classes[fc]:
            self.cmb_fx_wdg.blockSignals(True)
            for class_name in fb.filter_classes[fc]['fix']:  # get class name
                try:   # construct module + class name ...
                    mod_class_name = fb.fixpoint_classes[class_name]['mod'] + '.'\
                        + class_name
                    # ... and display name
                    disp_name = fb.fixpoint_classes[class_name]['name']
                    self.cmb_fx_wdg.addItem(disp_name, mod_class_name)
                    inst_wdg_str += '\t' + class_name + ' : ' + mod_class_name + '\n'
                except AttributeError as e:
                    logger.warning('Widget "{0}":\n{1}'.format(class_name, e))
                    self.embed_fixp_img(self.no_fx_filter_img)
                    continue  # with next `class_name` of for loop
                except KeyError as e:
                    logger.warning("No fixpoint filter for filter type {0} available."
                                   .format(e))
                    self.embed_fixp_img(self.no_fx_filter_img)
                    continue  # with next `class_name` of for loop

            # restore last fx widget if possible
            idx = self.cmb_fx_wdg.findText(last_fx_wdg)
            # set to idx 0 if not found (returned -1)
            self.cmb_fx_wdg.setCurrentIndex(max(idx, 0))
            self.cmb_fx_wdg.blockSignals(False)
        else:  # no fixpoint widget
            self.embed_fixp_img(self.no_fx_filter_img)
        self._update_fixp_widget()
        return inst_wdg_str

# # ------------------------------------------------------------------------------
#     def eventFilter(self, source, event):
#         """
#         Filter all events generated by monitored QLabel, only resize events are
#         processed here, generating a `sig_resize` signal. All other events
#         are passed on to the next hierarchy level.
#         """
#         if event.type() == QEvent.Resize:
#             logger.warning("resize event!")
#             self.sig_resize.emit()

#         # Call base class method to continue normal event processing:
#         return super(Input_Fixpoint_Specs, self).eventFilter(source, event)

# ------------------------------------------------------------------------------
    def embed_fixp_img(self, img_file: str) -> QPixmap:
        """
        Embed `img_file` in png format as `self.img_fixp`

        Parameters
        ----------
        img_file: str
            path and file name to image file

        Returns
        -------
        self.img_fixp: QPixmap object
            pixmap containing the passed img_file
        """
        if not os.path.isfile(img_file):
            logger.warning("Image file {0} doesn't exist.".format(img_file))
            img_file = self.default_fx_img

        _, file_extension = os.path.splitext(img_file)
        if file_extension != '.png':
            logger.error('Unknown file extension "{0}"!'.format(file_extension))
            img_file = self.default_fx_img

        self.img_fixp = QPixmap(img_file)
        # logger.warning(f"img_fixp = {img_file}")
        # logger.warning(f"_embed_fixp_img(): {self.img_fixp.__class__.__name__}")
        return self.img_fixp

# ------------------------------------------------------------------------------
    def resize_img(self) -> None:
        """
        Triggered when `self` (the widget) is selected or resized. The method resizes
        the image inside QLabel to completely fill the label while keeping
        the aspect ratio. An offset of some pixels is needed, otherwise the image
        is clipped.
        """
        # logger.warning(f"resize_img(): img_fixp = {self.img_fixp.__class__.__name__}")

        if self.parent is None:  # parent is QApplication, has no width or height
            par_w, par_h = 300, 700  # fixed size for module level test
        else:  # widget parent is InputTabWidget()
            par_w, par_h = self.parent.width(), self.parent.height()

        img_w, img_h = self.img_fixp.width(), self.img_fixp.height()

        if img_w > 10:
            max_h = int(max(np.floor(img_h * par_w/img_w) - 5, 20))
        else:
            max_h = 200
        logger.debug("img size: {0},{1}, frm size: {2},{3}, max_h: {4}"
                     .format(img_w, img_h, par_w, par_h, max_h))

        # The following doesn't work because the width of the parent widget can grow
        # with the image size
        # img_scaled = self.img_fixp.scaled(self.lbl_fixp_img.size(),
        # Qt.KeepAspectRatio, Qt.SmoothTransformation)
        img_scaled = self.img_fixp.scaledToHeight(max_h, Qt.SmoothTransformation)

        self.lbl_fixp_img.setPixmap(img_scaled)

# ------------------------------------------------------------------------------
    def _update_fixp_widget(self):
        """
        This method is called at the initialization of the widget and when
        a new fixpoint filter implementation is selected from the combo box:

        - Destruct old instance of fixpoint filter widget `self.fx_filt_ui`
        - Import and instantiate new fixpoint filter widget e.g. after changing the
          filter topology as
        - Try to load image for filter topology
        - Update the UI of the widget
        - Try to instantiate HDL filter as `self.fx_filt_ui.fixp_filter` with
            dummy data
        - emit {'fx_sim': 'specs_changed'} when successful
        """
        def _disable_fx_wdg(self) -> None:

            if hasattr(self, "fx_filt_ui") and self.fx_filt_ui is not None:
                # is a fixpoint widget loaded?
                try:
                    # try to remove widget from layout
                    self.layH_fx_wdg.removeWidget(self.fx_filt_ui)
                    # delete QWidget when scope has been left
                    self.fx_filt_ui.deleteLater()
                except AttributeError as e:
                    logger.error("Destructing UI failed!\n{0}".format(e))

            self.fx_wdg_found = False
            self.butSimFx.setEnabled(False)
            self.butExportHDL.setVisible(False)
            # self.layH_fx_wdg.setVisible(False)
            self.img_fixp = self.embed_fixp_img(self.no_fx_filter_img)
            self.resize_img()
            self.lblTitle.setText("")

            self.fx_filt_ui = None
        # -----------------------------------------------------------
        _disable_fx_wdg(self)  # destruct old fixpoint widget instance:

        # instantiate new fixpoint widget class as self.fx_filt_ui
        cmb_wdg_fx_cur = qget_cmb_box(self.cmb_fx_wdg, data=False)
        if cmb_wdg_fx_cur:  # at least one valid fixpoint widget found
            self.fx_wdg_found = True
            # get list [module name and path, class name]
            fx_mod_class_name = qget_cmb_box(self.cmb_fx_wdg, data=True).rsplit('.', 1)
            fx_mod = importlib.import_module(fx_mod_class_name[0])  # get module
            fx_filt_ui_class = getattr(fx_mod, fx_mod_class_name[1])  # get class
            logger.info("Instantiating new FX widget\n\t"
                        f"{fx_mod.__name__}.{fx_filt_ui_class.__name__}")
            # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            self.fx_filt_ui = fx_filt_ui_class()  # instantiate the fixpoint widget
            # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            # and add it to layout:
            self.layH_fx_wdg.addWidget(self.fx_filt_ui, stretch=1)
            self.fx_filt_ui.setVisible(True)
            self.wdg_dict2ui()  # initialize the fixpoint subwidgets from the fxqc_dict

            # ---- connect signals to fx_filt_ui ----
            if hasattr(self.fx_filt_ui, "sig_rx"):
                self.sig_rx.connect(self.fx_filt_ui.sig_rx)
            if hasattr(self.fx_filt_ui, "sig_tx"):
                self.fx_filt_ui.sig_tx.connect(self.sig_rx_local)

            # ---- get name of new fixpoint filter image ----
            if not (hasattr(self.fx_filt_ui, "img_name") and self.fx_filt_ui.img_name):
                # no image name defined, use default image
                img_file = self.default_fx_img
            else:
                # get path of imported fixpoint widget ...
                file_path = os.path.dirname(fx_mod.__file__)
                # ... and construct full image name from it
                img_file = os.path.join(file_path, self.fx_filt_ui.img_name)

            # ---- instantiate and scale graphic of filter topology ----
            self.embed_fixp_img(img_file)
            self.resize_img()

            # ---- set title and description for filter
            self.lblTitle.setText(self.fx_filt_ui.title)

            # Check which methods the fixpoint widget provides and enable
            # corresponding buttons:
            self.butExportHDL.setVisible(hasattr(self.fx_filt_ui, "to_hdl"))
            self.butSimFx.setEnabled(hasattr(self.fx_filt_ui, "fxfilter"))
            self.update_fxqc_dict()
            self.emit({'fx_sim': 'specs_changed'})

# ------------------------------------------------------------------------------
    def wdg_dict2ui(self):
        """
        Trigger an update of the fixpoint widget UI when view (i.e. fixpoint
        coefficient format) or data have been changed outside this class. Additionally,
        pass the fixpoint quantization widget to update / restore other subwidget
        settings.

        Set the RUN button to "changed".
        """
#        fb.fil[0]['fxqc']['QCB'].update({'scale':(1 << fb.fil[0]['fxqc']['QCB']['W'])})
        self.wdg_q_input.dict2ui(fb.fil[0]['fxqc']['QI'])
        self.wdg_q_output.dict2ui(fb.fil[0]['fxqc']['QO'])
        self.wdg_w_input.dict2ui(fb.fil[0]['fxqc']['QI'])
        self.wdg_w_output.dict2ui(fb.fil[0]['fxqc']['QO'])
        if self.fx_wdg_found and hasattr(self.fx_filt_ui, "dict2ui"):
            self.fx_filt_ui.dict2ui()
#            dict_sig = {'fx_sim':'specs_changed'}
#            self.emit(dict_sig)

        qstyle_widget(self.butSimFx, "changed")

# ------------------------------------------------------------------------------
    def update_fxqc_dict(self):
        """
        Update the fxqc dictionary before simulation / HDL generation starts.
        """
        if self.fx_wdg_found:
            # get a dict with the coefficients and fixpoint settings from fixpoint widget
            if hasattr(self.fx_filt_ui, "ui2dict"):
                fb.fil[0]['fxqc'].update(self.fx_filt_ui.ui2dict())
                logger.debug("update fxqc: \n{0}".format(pprint_log(fb.fil[0]['fxqc'])))
        else:
            logger.error("No fixpoint widget found!")

# ------------------------------------------------------------------------------
    def exportHDL(self):
        """
        Synthesize HDL description of filter
        """
        dlg = QFD(self)  # instantiate file dialog object

        file_types = "Verilog (*.v)"
        # needed for overwrite confirmation when name is entered without suffix:
        dlg.setDefaultSuffix('v')
        dlg.setWindowTitle('Export Verilog')
        dlg.setNameFilter(file_types)
        dlg.setDirectory(dirs.save_dir)
        # set mode "save file" instead "open file":
        dlg.setAcceptMode(QFD.AcceptSave)
        dlg.setOption(QFD.DontConfirmOverwrite, False)
        if dlg.exec_() == QFD.Accepted:
            hdl_file = qstr(dlg.selectedFiles()[0])
            # hdl_type = extract_file_ext(qstr(dlg.selectedNameFilter()))[0]

# =============================================================================
#       # static method getSaveFileName_() is simple but unflexible
#         hdl_file, hdl_filter = dlg.getSaveFileName_(
#                 caption="Save Verilog netlist as (this also defines the module name)",
#                 directory=dirs.save_dir, filter=file_types)
#         hdl_file = qstr(hdl_file)
#         if hdl_file != "": # "operation cancelled" returns an empty string
#             # return '.v' or '.vhd' depending on filetype selection:
#             # hdl_type = extract_file_ext(qstr(hdl_filter))[0]
#             # sanitized dir + filename + suffix. The filename suffix is replaced
#             # by `v` later.
#             hdl_file = os.path.normpath(hdl_file) # complete path + file name
# =============================================================================
            hdl_dir_name = os.path.dirname(hdl_file)  # extract the directory path
            if not os.path.isdir(hdl_dir_name):  # create directory if it doesn't exist
                os.mkdir(hdl_dir_name)
            dirs.save_dir = hdl_dir_name  # make this directory the new default / base dir
            hdl_file_name = os.path.splitext(os.path.basename(hdl_file))[0]
            hdl_full_name = os.path.join(hdl_dir_name, hdl_file_name + ".v")
            # remove all non-alphanumeric chars:
            vlog_mod_name = re.sub(r'\W+', '', hdl_file_name).lower()

            logger.info('Creating hdl_file "{0}"\n\twith top level module "{1}"'
                        .format(hdl_full_name, vlog_mod_name))
            try:
                self.update_fxqc_dict()
                self.fx_filt_ui.construct_fixp_filter()
                code = self.fx_filt_ui.to_hdl(name=vlog_mod_name)
                # logger.info(str(code)) # print verilog code to console
                with io.open(hdl_full_name, 'w', encoding="utf8") as f:
                    f.write(str(code))

                logger.info("HDL conversion finished!")
            except (IOError, TypeError) as e:
                logger.warning(e)

    # --------------------------------------------------------------------------
    def fx_sim_init(self):
        """
        Initialize fix-point simulation:

        - Update the `fxqc_dict` containing all quantization information
        - Setup a filter instance for fixpoint simulation
        - Request a stimulus signal

        Returns
        -------
        error: int
            0 for sucessful fx widget construction, -1 for error
        """
        try:
            self.update_fxqc_dict()
            self.fx_filt_ui.init_filter()   # setup filter instance
            return 0

        except ValueError as e:
            logger.error('Fixpoint stimulus generation failed during "init"'
                         '\nwith "{0} "'.format(e))
        return -1

# ------------------------------------------------------------------------------
    def fx_sim_calc_response(self, dict_sig) -> None:
        """
        - Read fixpoint stimulus from `dict_sig` in integer format
        - Pass it to the fixpoint filter which calculates the fixpoint response
        - Store the result in `fb.fx_results` and return. In case of an error,
          `fb.fx_results == None`

        Returns
        -------
        None
        """
        try:
            # logger.info(
            #     'Simulate fixpoint frame with "{0}" stimulus:\n\t{1}'.format(
            #         dict_sig['class'],
            #         pprint_log(dict_sig['fx_stimulus'], tab=" "),
            #         ))

            # Run fixpoint simulation and store the results as integer values:
            fb.fx_results = self.fx_filt_ui.fxfilter(dict_sig['fx_stimulus'])

            if len(fb.fx_results) == 0:
                logger.error("Fixpoint simulation returned empty results!")
            # else:
            #     # logger.debug("fx_results: {0}"\
            #     #            .format(pprint_log(fb.fx_results, tab= " ")))
            #     logger.info(
            #         f'Fixpoint simulation successful for dict\n{pprint_log(dict_sig)}'
            #         f'\tStimuli: Shape {np.shape(dict_sig["fx_stimulus"])}'
            #         f' of type "{dict_sig["fx_stimulus"].dtype}"'
            #         f'\n\tResponse: Shape {np.shape(fb.fx_results)}'
            #         f' of type "{type(fb.fx_results).__name__} "'
            #         f' ("{type(fb.fx_results[0]).__name__}")'
            #     )

        except ValueError as e:
            logger.error("Simulator error {0}".format(e))
            fb.fx_results = None

        except AssertionError as e:
            logger.error('Fixpoint simulation failed for dict\n{0}'
                         '\twith msg. "{1}"\n\tStimuli: Shape {2} of type "{3}"'
                         '\n\tResponse: Shape {4} of type "{5}"'.format(
                            pprint_log(dict_sig), e,
                            np.shape(dict_sig['fx_stimulus']),
                            dict_sig['fx_stimulus'].dtype,
                            np.shape(fb.fx_results),
                            type(fb.fx_results)
                                ))
            fb.fx_results = None

        if fb.fx_results is None:
            qstyle_widget(self.butSimFx, "error")
        else:
            pass # everything ok, return 
            # logger.debug("Sending fixpoint results")
        return
Exemple #3
0
class Input_Fixpoint_Specs(QWidget):
    """
    Create the widget that holds the dynamically loaded fixpoint filter ui 
    """
    # emit a signal when the image has been resized
    sig_resize = pyqtSignal()
    # incoming from subwidgets -> process_sig_rx_local
    sig_rx_local = pyqtSignal(object)
    # incoming, connected to input_tab_widget.sig_rx
    sig_rx = pyqtSignal(object)
    # outcgoing
    sig_tx = pyqtSignal(object)

    def __init__(self, parent):
        super(Input_Fixpoint_Specs, self).__init__(parent)

        self.tab_label = 'Fixpoint'
        self.tool_tip = (
            "<span>Select a fixpoint implementation for the filter,"
            " simulate it or generate a Verilog netlist.</span>")
        self.parent = parent
        self.fx_path = os.path.realpath(
            os.path.join(dirs.INSTALL_DIR, 'fixpoint_widgets'))
        self.no_fx_filter_img = os.path.join(self.fx_path, "no_fx_filter.png")
        if not os.path.isfile(self.no_fx_filter_img):
            logger.error("Image {0:s} not found!".format(
                self.no_fx_filter_img))

        self.default_fx_img = os.path.join(self.fx_path, "default_fx_img.png")
        if not os.path.isfile(self.default_fx_img):
            logger.error("Image {0:s} not found!".format(self.default_fx_img))

        if HAS_MIGEN:
            self._construct_UI()
        else:
            self.state = "deactivated"  # "invisible", "disabled"

#------------------------------------------------------------------------------

    def process_sig_rx(self, dict_sig=None):
        """
        Process signals coming in via subwidgets and sig_rx
		
		Play PingPong with a stimulus & plot widget:
        
		2. ``fx_sim_init()``: Request stimulus by sending 'fx_sim':'get_stimulus'
		
		3. ``fx_sim_set_stimulus()``: Receive stimulus from widget in 'fx_sim':'send_stimulus'
			and pass it to HDL object for simulation
		   
		4. Send back HDL response to widget via 'fx_sim':'set_response'

        """

        logger.debug("process_sig_rx(): vis={0}\n{1}"\
                    .format(self.isVisible(), pprint_log(dict_sig)))
        if dict_sig['sender'] == __name__:
            logger.debug("Stopped infinite loop\n{0}".format(
                pprint_log(dict_sig)))
            return
        elif 'data_changed' in dict_sig and dict_sig[
                'data_changed'] == "filter_designed":
            # New filter has been designed, update list of available filter topologies here
            self._update_filter_cmb()
            return
        elif 'data_changed' in dict_sig or\
            ('view_changed' in dict_sig and dict_sig['view_changed'] == 'q_coeff'):
            # update fields in the filter topology widget - wordlength may have
            # been changed. Also set RUN button to "changed" in wdg_dict2ui()
            self.wdg_dict2ui()
            #self.sig_tx.emit({'sender':__name__, 'fx_sim':'specs_changed'})
        elif 'fx_sim' in dict_sig:
            if dict_sig['fx_sim'] == 'init':
                if self.fx_wdg_found:
                    self.fx_sim_init()
                else:
                    logger.error("No fixpoint widget found!")
                    qstyle_widget(self.butSimHDL, "error")
                    self.sig_tx.emit({'sender': __name__, 'fx_sim': 'error'})

            elif dict_sig['fx_sim'] == 'send_stimulus':
                self.fx_sim_set_stimulus(dict_sig)
            elif dict_sig['fx_sim'] == 'specs_changed':
                # fixpoint specification have been changed somewhere, update ui
                # and set run button to "changed" in wdg_dict2ui()
                self.wdg_dict2ui()
            elif dict_sig['fx_sim'] == 'finish':
                qstyle_widget(self.butSimHDL, "normal")
                logger.info('Fixpoint simulation [{0:5.3g} ms]: Plotting finished'\
                            .format((time.process_time() - self.t_resp)*1000))
            else:
                logger.error('Unknown "fx_sim" command option "{0}"\n'
                             '\treceived from "{1}".'.format(
                                 dict_sig['fx_sim'], dict_sig['sender']))
        # ---- Process local widget signals
        elif 'ui' in dict_sig:
            if 'id' in dict_sig and dict_sig['id'] == 'w_input':
                """
                Input fixpoint format has been changed or butLock has been clicked.
                When I/O lock is active, copy input fixpoint word format to output 
                word format.
                """
                if dict_sig[
                        'ui'] == 'butLock' and not self.wdg_w_input.butLock.isChecked(
                        ):
                    # butLock was deactivitated, don't do anything
                    return
                elif self.wdg_w_input.butLock.isChecked():
                    # but lock was activated or wordlength setting have been changed
                    fb.fil[0]['fxqc']['QO']['WI'] = fb.fil[0]['fxqc']['QI'][
                        'WI']
                    fb.fil[0]['fxqc']['QO']['WF'] = fb.fil[0]['fxqc']['QI'][
                        'WF']
                    fb.fil[0]['fxqc']['QO']['W'] = fb.fil[0]['fxqc']['QI']['W']

            elif 'id' in dict_sig and dict_sig['id'] == 'w_output':
                """
                Output fixpoint format has been changed. When I/O lock is active, copy
                output fixpoint word format to input word format.
                """
                if self.wdg_w_input.butLock.isChecked():
                    fb.fil[0]['fxqc']['QI']['WI'] = fb.fil[0]['fxqc']['QO'][
                        'WI']
                    fb.fil[0]['fxqc']['QI']['WF'] = fb.fil[0]['fxqc']['QO'][
                        'WF']
                    fb.fil[0]['fxqc']['QI']['W'] = fb.fil[0]['fxqc']['QO']['W']

            elif 'id' in dict_sig and dict_sig['id'] in \
                {'w_coeff', 'q_input', 'q_output', 'w_accu', 'q_accu'}:
                pass  # nothing to do for now

            else:
                if not "id" in dict_sig:
                    logger.warning("No id in dict_sig:\n{0}".format(
                        pprint_log(dict_sig)))
                else:
                    logger.warning('Unknown id "{0}" in dict_sig:\n{1}'\
                                   .format(dict_sig['id'], pprint_log(dict_sig)))

            if not dict_sig['ui'] in {
                    'WI', 'WF', 'ovfl', 'quant', 'cmbW', 'butLock'
            }:
                logger.warning("Unknown value '{0}' for key 'ui'".format(
                    dict_sig['ui']))
            self.wdg_dict2ui(
            )  # update wordlengths in UI and set RUN button to 'changed'
            self.sig_tx.emit({'sender': __name__, 'fx_sim': 'specs_changed'})

            return

#------------------------------------------------------------------------------

    def _construct_UI(self):
        """
        Intitialize the main GUI, consisting of:
            
        - A combo box to select the filter topology and an image of the topology
        
        - The input quantizer
        
        - The UI of the fixpoint filter widget
        
        - Simulation and export buttons
        """
        #------------------------------------------------------------------------------
        # Define frame and layout for the dynamically updated filter widget
        # The actual filter widget is instantiated in self.set_fixp_widget() later on

        self.layH_fx_wdg = QHBoxLayout()
        #self.layH_fx_wdg.setContentsMargins(*params['wdg_margins'])
        frmHDL_wdg = QFrame(self)
        frmHDL_wdg.setLayout(self.layH_fx_wdg)
        #frmHDL_wdg.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        #------------------------------------------------------------------------------
        #       Initialize fixpoint filter combobox, title and description
        #------------------------------------------------------------------------------
        self.cmb_wdg_fixp = QComboBox(self)
        self.cmb_wdg_fixp.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.lblTitle = QLabel("not set", self)
        self.lblTitle.setWordWrap(True)
        self.lblTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        layHTitle = QHBoxLayout()
        layHTitle.addWidget(self.cmb_wdg_fixp)
        layHTitle.addWidget(self.lblTitle)

        self.frmTitle = QFrame(self)
        self.frmTitle.setLayout(layHTitle)
        self.frmTitle.setContentsMargins(*params['wdg_margins'])

        #------------------------------------------------------------------------------
        #       Input and Output Quantizer
        #------------------------------------------------------------------------------
        #       - instantiate widgets for input and output quantizer
        #       - pass the quantization (sub-?) dictionary to the constructor
        #------------------------------------------------------------------------------

        self.wdg_w_input = UI_W(self,
                                q_dict=fb.fil[0]['fxqc']['QI'],
                                id='w_input',
                                label='',
                                lock_visible=True)
        self.wdg_w_input.sig_tx.connect(self.process_sig_rx)

        cmb_q = ['round', 'floor', 'fix']

        self.wdg_w_output = UI_W(self,
                                 q_dict=fb.fil[0]['fxqc']['QO'],
                                 id='w_output',
                                 label='')
        self.wdg_w_output.sig_tx.connect(self.process_sig_rx)

        self.wdg_q_output = UI_Q(
            self,
            q_dict=fb.fil[0]['fxqc']['QO'],
            id='q_output',
            label='Output Format <i>Q<sub>Y&nbsp;</sub></i>:',
            cmb_q=cmb_q,
            cmb_ov=['wrap', 'sat'])
        self.wdg_q_output.sig_tx.connect(self.sig_rx)

        if HAS_DS:
            cmb_q.append('dsm')
        self.wdg_q_input = UI_Q(
            self,
            q_dict=fb.fil[0]['fxqc']['QI'],
            id='q_input',
            label='Input Format <i>Q<sub>X&nbsp;</sub></i>:',
            cmb_q=cmb_q)
        self.wdg_q_input.sig_tx.connect(self.sig_rx)

        # Layout and frame for input quantization
        layVQiWdg = QVBoxLayout()
        layVQiWdg.addWidget(self.wdg_q_input)
        layVQiWdg.addWidget(self.wdg_w_input)
        frmQiWdg = QFrame(self)
        #frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQiWdg.setLayout(layVQiWdg)
        frmQiWdg.setContentsMargins(*params['wdg_margins'])

        # Layout and frame for output quantization
        layVQoWdg = QVBoxLayout()
        layVQoWdg.addWidget(self.wdg_q_output)
        layVQoWdg.addWidget(self.wdg_w_output)
        frmQoWdg = QFrame(self)
        #frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQoWdg.setLayout(layVQoWdg)
        frmQoWdg.setContentsMargins(*params['wdg_margins'])

        #------------------------------------------------------------------------------
        #       Dynamically updated image of filter topology
        #------------------------------------------------------------------------------
        # label is a placeholder for image
        self.lbl_fixp_img = QLabel("img not set", self)
        #self.lbl_fixp_img.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.embed_fixp_img(self.no_fx_filter_img)

        layHImg = QHBoxLayout()
        layHImg.setContentsMargins(0, 0, 0, 0)
        layHImg.addWidget(self.lbl_fixp_img)  #, Qt.AlignCenter)
        self.frmImg = QFrame(self)
        self.frmImg.setLayout(layHImg)
        self.frmImg.setContentsMargins(*params['wdg_margins'])
        self.resize_img()
        #------------------------------------------------------------------------------
        #       Simulation and export Buttons
        #------------------------------------------------------------------------------
        self.butExportHDL = QPushButton(self)
        self.butExportHDL.setToolTip(
            "Export fixpoint filter in Verilog format.")
        self.butExportHDL.setText("Create HDL")

        self.butSimHDL = QPushButton(self)
        self.butSimHDL.setToolTip("Start migen fixpoint simulation.")
        self.butSimHDL.setText("Sim. HDL")

        self.butSimFxPy = QPushButton(self)
        self.butSimFxPy.setToolTip("Simulate filter with fixpoint effects.")
        self.butSimFxPy.setText("Sim. FixPy")

        self.layHHdlBtns = QHBoxLayout()
        self.layHHdlBtns.addWidget(self.butSimFxPy)
        self.layHHdlBtns.addWidget(self.butSimHDL)
        self.layHHdlBtns.addWidget(self.butExportHDL)
        # This frame encompasses the HDL buttons sim and convert
        frmHdlBtns = QFrame(self)
        #frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmHdlBtns.setLayout(self.layHHdlBtns)
        frmHdlBtns.setContentsMargins(*params['wdg_margins'])

        # -------------------------------------------------------------------
        #       Top level layout
        # -------------------------------------------------------------------
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(frmHDL_wdg)
        splitter.addWidget(frmQoWdg)
        splitter.addWidget(self.frmImg)

        # setSizes uses absolute pixel values, but can be "misused" by specifying values
        # that are way too large: in this case, the space is distributed according
        # to the _ratio_ of the values:
        splitter.setSizes([3000, 3000, 5000])

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmTitle)
        layVMain.addWidget(frmHdlBtns)
        layVMain.addWidget(frmQiWdg)
        layVMain.addWidget(splitter)
        layVMain.addStretch()
        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs & EVENTFILTERS
        #----------------------------------------------------------------------
        # monitor events and generate sig_resize event when resized
        self.lbl_fixp_img.installEventFilter(self)
        # ... then redraw image when resized
        self.sig_resize.connect(self.resize_img)

        self.cmb_wdg_fixp.currentIndexChanged.connect(self._update_fixp_widget)

        self.butExportHDL.clicked.connect(self.exportHDL)
        self.butSimHDL.clicked.connect(self.fx_sim_init)
        #----------------------------------------------------------------------
        inst_wdg_list = self._update_filter_cmb()
        if len(inst_wdg_list) == 0:
            logger.warning("No fixpoint filters found!")
        else:
            logger.debug("Imported {0:d} fixpoint filters:\n{1}".format(
                len(inst_wdg_list.split("\n")) - 1, inst_wdg_list))

        self._update_fixp_widget()

#------------------------------------------------------------------------------

    def _update_filter_cmb(self):
        """
        (Re-)Read list of available fixpoint filters for a given filter design 
        every time a new filter design is selected. 
        
        Then try to import the fixpoint designs in the list and populate the 
        fixpoint implementation combo box `self.cmb_wdg_fixp` when successfull. 
        """
        inst_wdg_str = ""  # full names of successfully instantiated widgets for logging
        last_fx_wdg = qget_cmb_box(
            self.cmb_wdg_fixp, data=False)  # remember last fx widget setting
        self.cmb_wdg_fixp.clear()
        fc = fb.fil[0]['fc']
        if 'fix' in fb.filter_classes[fc]:
            for class_name in fb.filter_classes[fc]['fix']:  # get class name
                try:
                    # construct module + class name
                    mod_class_name = fb.fixpoint_classes[class_name][
                        'mod'] + '.' + class_name
                    disp_name = fb.fixpoint_classes[class_name][
                        'name']  # # and display name
                    self.cmb_wdg_fixp.addItem(disp_name, mod_class_name)
                    inst_wdg_str += '\t' + class_name + ' : ' + mod_class_name + '\n'
                except AttributeError as e:
                    logger.warning('Widget "{0}":\n{1}'.format(class_name, e))
                    self.embed_fixp_img(self.no_fx_filter_img)
                    continue
                except KeyError as e:
                    logger.warning(
                        "No fixpoint filter for filter type {0} available.".
                        format(e))
                    self.embed_fixp_img(self.no_fx_filter_img)
                    continue

        # restore last fxp widget if possible
            idx = self.cmb_wdg_fixp.findText(last_fx_wdg)
            # set to idx 0 if not found (returned -1)
            self.cmb_wdg_fixp.setCurrentIndex(max(idx, 0))
        else:  # no fixpoint widget
            self.embed_fixp_img(self.no_fx_filter_img)
        return inst_wdg_str

#------------------------------------------------------------------------------

    def eventFilter(self, source, event):
        """
        Filter all events generated by monitored QLabel, only resize events are
        processed here, generating a `sig_resize` signal. All other events
        are passed on to the next hierarchy level.
        """
        if event.type() == QEvent.Resize:
            self.sig_resize.emit()

        # Call base class method to continue normal event processing:
        return super(Input_Fixpoint_Specs, self).eventFilter(source, event)
#------------------------------------------------------------------------------

    def embed_fixp_img(self, img_file):
        """ 
        Embed image as self.img_fixp, either in png or svg format
        
        Parameters:
            
            img_file: str
            path and file name to image file
        """
        if not os.path.isfile(img_file):
            logger.warning("Image file {0} doesn't exist.".format(img_file))
            img_file = self.default_fx_img

#        _, file_extension = os.path.splitext(self.fx_wdg_inst.img_name)
        _, file_extension = os.path.splitext(img_file)
        if file_extension == '.png':
            self.img_fixp = QPixmap(img_file)
            #self.lbl_fixp_img.setPixmap(QPixmap(self.img_fixp)) # fixed size
        # elif file_extension == '.svg':
        #     self.img_fixp = QtSvg.QSvgWidget(img_file)

        else:
            logger.error(
                'Unknown file extension "{0}"!'.format(file_extension))

        self.resize_img()

#------------------------------------------------------------------------------

    def resize_img(self):
        """ 
        Triggered when self (the widget) is resized, consequently the image
        inside QLabel is resized to completely fill the label while keeping 
        the aspect ratio.
        
        This doesn't really work at the moment.
        """

        if hasattr(self.parent, "width"):  # needed for module test
            par_w, par_h = self.parent.width(), self.parent.height()
        else:
            par_w, par_h = 300, 700  # fixed size for module testself.lbl_img_fixp
        lbl_w, lbl_h = self.lbl_fixp_img.width(), self.lbl_fixp_img.height()
        img_w, img_h = self.img_fixp.width(), self.img_fixp.height()

        if img_w > 10:
            max_h = int(max(np.floor(img_h * par_w / img_w) - 15, 20))
        else:
            max_h = 200
        logger.debug("img size: {0},{1}, frm size: {2},{3}, max_h: {4}".format(
            img_w, img_h, par_w, par_h, max_h))

        # The following doesn't work because the width of the parent widget can grow
        # with the image size
        # img_scaled = self.img_fixp.scaled(self.lbl_fixp_img.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
        img_scaled = self.img_fixp.scaledToHeight(max_h,
                                                  Qt.SmoothTransformation)
        #img_scaled = self.img_fixp.scaledToHeight(max_h)

        self.lbl_fixp_img.setPixmap(img_scaled)

#------------------------------------------------------------------------------

    def _update_fixp_widget(self):
        """
        This method is called at the initialization of the widget and when
        a new fixpoint filter implementation is selected from the combo box:

        - Destruct old instance of fixpoint filter widget `self.fx_wdg_inst`

        - Import and instantiate new fixpoint filter widget e.g. after changing the 
          filter topology as 

        - Try to load image for filter topology

        - Update the UI of the widget

        - Try to instantiate HDL filter as `self.fx_wdg_inst.fixp_filter` with 
            dummy data
        """
        def _disable_fx_wdg(self):

            if hasattr(
                    self, "fx_wdg_inst"
            ) and self.fx_wdg_inst is not None:  # is a fixpoint widget loaded?
                try:
                    self.layH_fx_wdg.removeWidget(
                        self.fx_wdg_inst)  # remove widget from layout
                    self.fx_wdg_inst.deleteLater(
                    )  # delete QWidget when scope has been left
                except AttributeError as e:
                    logger.error("Destructing UI failed!\n{0}".format(e))

            self.fx_wdg_found = False
            self.butSimFxPy.setVisible(False)
            self.butSimHDL.setEnabled(False)
            self.butExportHDL.setEnabled(False)
            #self.layH_fx_wdg.setVisible(False)
            self.img_fixp = self.embed_fixp_img(self.no_fx_filter_img)
            self.lblTitle.setText("")

            self.fx_wdg_inst = None

        # destruct old fixpoint widget instance
        _disable_fx_wdg(self)

        # instantiate new fixpoint widget class as self.fx_wdg_inst
        cmb_wdg_fx_cur = qget_cmb_box(self.cmb_wdg_fixp, data=False)
        if cmb_wdg_fx_cur:  # at least one valid fixpoint widget found
            self.fx_wdg_found = True
            # get list [module name and path, class name]
            fx_mod_class_name = qget_cmb_box(self.cmb_wdg_fixp,
                                             data=True).rsplit('.', 1)
            fx_mod = importlib.import_module(
                fx_mod_class_name[0])  # get module
            fx_wdg_class = getattr(fx_mod, fx_mod_class_name[1])  # get class
            #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            self.fx_wdg_inst = fx_wdg_class(
                self)  # instantiate the fixpoint widget
            #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            self.layH_fx_wdg.addWidget(self.fx_wdg_inst,
                                       stretch=1)  # and add it to layout
            self.fx_wdg_inst.setVisible(True)
            # Doesn't work at the moment, combo box becomes inaccessible
            #            try:
            #                self.fx_wdg_inst = fx_wdg_class(self) # instantiate the widget
            #                self.layH_fx_wdg.addWidget(self.fx_wdg_inst, stretch=1) # and add it to layout
            #            except KeyError as e:
            #                logger.warning('Key Error {0} in fixpoint filter \n{1}'\
            #                               .format(e, fx_mod_name + "." + cmb_wdg_fx_cur))
            #                _disable_fx_wdg(self)
            #                return

            self.wdg_dict2ui(
            )  # initialize the fixpoint subwidgets from the fxqc_dict

            #---- connect signals to fx_wdg_inst ----
            if hasattr(self.fx_wdg_inst, "sig_rx"):
                self.sig_rx.connect(self.fx_wdg_inst.sig_rx)
            if hasattr(self.fx_wdg_inst, "sig_tx"):
                self.fx_wdg_inst.sig_tx.connect(self.sig_rx)

            #---- get name of new fixpoint filter image ----
            if not (hasattr(self.fx_wdg_inst, "img_name") and
                    self.fx_wdg_inst.img_name):  # is an image name defined?
                img_file = self.default_fx_img
            else:
                file_path = os.path.dirname(
                    fx_mod.__file__
                )  # get path of imported fixpoint widget and
                img_file = os.path.join(file_path, self.fx_wdg_inst.img_name
                                        )  # construct full image name from it

        #---- instantiate and scale graphic of filter topology ----
            self.embed_fixp_img(img_file)

            #---- set title and description for filter
            self.lblTitle.setText(self.fx_wdg_inst.title)

            #--- try to reference Python fixpoint filter instance -----
            #            if hasattr(self.fx_wdg_inst,'fxpy_filter'):
            #                self.fxpy_filter_inst = self.fx_wdg_inst.fxpy_filter
            #                self.butSimFxPy.setEnabled(True)
            #            else:
            #                self.butSimFxPy.setVisible(False)

            #--- Check whether fixpoint widget contains HDL filters -----
            if hasattr(self.fx_wdg_inst, 'fixp_filter'):
                self.butExportHDL.setEnabled(
                    hasattr(self.fx_wdg_inst, "to_verilog"))
                self.butSimHDL.setEnabled(hasattr(self.fx_wdg_inst, "run_sim"))
                self.update_fxqc_dict()
                self.sig_tx.emit({
                    'sender': __name__,
                    'fx_sim': 'specs_changed'
                })
            else:
                self.butSimHDL.setEnabled(False)
                self.butExportHDL.setEnabled(False)

        else:
            _disable_fx_wdg(self)

#------------------------------------------------------------------------------

    def wdg_dict2ui(self):
        """
        Trigger an update of the fixpoint widget UI when view (i.e. fixpoint 
        coefficient format) or data have been changed outside this class. Additionally,
        pass the fixpoint quantization widget to update / restore other subwidget
        settings.
        
        Set the RUN button to "changed".
        """
        #        fb.fil[0]['fxqc']['QCB'].update({'scale':(1 << fb.fil[0]['fxqc']['QCB']['W'])})
        self.wdg_q_input.dict2ui(fb.fil[0]['fxqc']['QI'])
        self.wdg_q_output.dict2ui(fb.fil[0]['fxqc']['QO'])
        self.wdg_w_input.dict2ui(fb.fil[0]['fxqc']['QI'])
        self.wdg_w_output.dict2ui(fb.fil[0]['fxqc']['QO'])
        if self.fx_wdg_found and hasattr(self.fx_wdg_inst, "dict2ui"):
            self.fx_wdg_inst.dict2ui()
#            dict_sig = {'sender':__name__, 'fx_sim':'specs_changed'}
#            self.sig_tx.emit(dict_sig)

        qstyle_widget(self.butSimHDL, "changed")
#------------------------------------------------------------------------------

    def update_fxqc_dict(self):
        """
        Update the fxqc dictionary before simulation / HDL generation starts.
        """
        if self.fx_wdg_found:
            # get a dict with the coefficients and fixpoint settings from fixpoint widget
            if hasattr(self.fx_wdg_inst, "ui2dict"):
                fb.fil[0]['fxqc'].update(self.fx_wdg_inst.ui2dict())
                logger.debug("update fxqc: \n{0}".format(
                    pprint_log(fb.fil[0]['fxqc'])))
        else:
            logger.error("No fixpoint widget found!")
#------------------------------------------------------------------------------

    def exportHDL(self):
        """
        Synthesize HDL description of filter
        """
        if not hasattr(self.fx_wdg_inst, 'construct_fixp_filter'):
            logger.warning(
                'Fixpoint widget has no method "construct_fixp_filter", aborting.'
            )
            return

        dlg = QFD(self)  # instantiate file dialog object

        file_types = "Verilog (*.v)"
        dlg.setDefaultSuffix(
            'v'
        )  # needed for overwrite confirmation when name is entered without suffix
        dlg.setWindowTitle('Export Vlog')
        dlg.setNameFilter(file_types)
        dlg.setDirectory(dirs.save_dir)
        dlg.setAcceptMode(
            QFD.AcceptSave)  # set mode "save file" instead "open file"
        dlg.setOption(QFD.DontConfirmOverwrite, False)
        if dlg.exec_() == QFD.Accepted:
            hdl_file = qstr(dlg.selectedFiles()[0])
            # hdl_type = extract_file_ext(qstr(dlg.selectedNameFilter()))[0]

            # =============================================================================
            #       # static method getSaveFileName_() is simple but unflexible
            #         hdl_file, hdl_filter = dlg.getSaveFileName_(
            #                 caption="Save Verilog netlist as (this also defines the module name)",
            #                 directory=dirs.save_dir, filter=file_types)
            #         hdl_file = qstr(hdl_file)
            #         if hdl_file != "": # "operation cancelled" returns an empty string
            #             # return '.v' or '.vhd' depending on filetype selection:
            #             # hdl_type = extract_file_ext(qstr(hdl_filter))[0]
            #             # sanitized dir + filename + suffix. The filename suffix is replaced
            #             # by `v` later.
            #             hdl_file = os.path.normpath(hdl_file) # complete path + file name
            # =============================================================================
            hdl_dir_name = os.path.dirname(
                hdl_file)  # extract the directory path
            if not os.path.isdir(
                    hdl_dir_name):  # create directory if it doesn't exist
                os.mkdir(hdl_dir_name)
            dirs.save_dir = hdl_dir_name  # make this directory the new default / base dir
            hdl_file_name = os.path.splitext(os.path.basename(hdl_file))[0]
            hdl_full_name = os.path.join(hdl_dir_name, hdl_file_name + ".v")
            vlog_mod_name = re.sub(
                r'\W+', '',
                hdl_file_name).lower()  # remove all non-alphanumeric chars

            logger.info(
                'Creating hdl_file "{0}"\n\twith top level module "{1}"'.
                format(hdl_full_name, vlog_mod_name))
            try:
                self.update_fxqc_dict()
                self.fx_wdg_inst.construct_fixp_filter()
                code = self.fx_wdg_inst.to_verilog(name=vlog_mod_name)
                #logger.info(str(code)) # print verilog code to console
                with io.open(hdl_full_name, 'w', encoding="utf8") as f:
                    f.write(str(code))

                logger.info("HDL conversion finished!")
            except (IOError, TypeError) as e:
                logger.warning(e)

##------------------------------------------------------------------------------
#    def fx_sim_py(self):
#        """
#        Start fix-point simulation: Send the ``fxqc_dict``
#        containing all quantization information and request a stimulus signal
#        Not implemented yet
#        """
#        try:
#            logger.info("Started python fixpoint simulation")
#            self.update_fxqc_dict()
#            self.fxpyfilter.setup(fb.fil[0]['fxqc'])   # setup filter instance
#            dict_sig = {'sender':__name__, 'fx_sim':'get_stimulus'}
#            self.sig_tx.emit(dict_sig)
#
#        except AttributeError as e:
#            logger.warning("Fixpoint stimulus generation failed:\n{0}".format(e))
#        return

#------------------------------------------------------------------------------

    def fx_sim_init(self):
        """
        Initialize fix-point simulation: 
            
        - Update the `fxqc_dict` containing all quantization information
        
        - Setup a filter instance for migen simulation
        
        - Request a stimulus signal
        """
        if not hasattr(self.fx_wdg_inst, 'construct_fixp_filter'):
            logger.error(
                'Fixpoint widget has no method "construct_fixp_filter", aborting.'
            )
            self.sig_tx.emit({'sender': __name__, 'fx_sim': 'error'})
            return

        try:
            logger.info("Fixpoint simulation started")
            self.t_start = time.process_time()
            self.update_fxqc_dict()
            self.fx_wdg_inst.construct_fixp_filter()  # setup filter instance

            dict_sig = {'sender': __name__, 'fx_sim': 'get_stimulus'}
            self.sig_tx.emit(dict_sig)

        except ValueError as e:  # exception
            logger.error(
                'Fixpoint stimulus generation failed during "init" for dict\n{0}'
                '\nwith "{1} "'.format(pprint_log(dict_sig), e))
        return

#------------------------------------------------------------------------------

    def fx_sim_set_stimulus(self, dict_sig):
        """
        - Get fixpoint stimulus from `dict_sig` in integer format
          
        - Pass it to the fixpoint filter and calculate the fixpoint response
        
        - Send the reponse to the plotting widget
        """
        try:
            logger.debug(
                'Starting fixpoint simulation with stimulus from "{0}":\n\tfx_stimulus:{1}'
                '\n\tStimuli: Shape {2} of type "{3}"'.format(
                    dict_sig['sender'],
                    pprint_log(dict_sig['fx_stimulus'], tab=" "),
                    np.shape(dict_sig['fx_stimulus']),
                    dict_sig['fx_stimulus'].dtype,
                ))
            self.t_stim = time.process_time()
            logger.info("Fixpoint simulation [{0:5.3g} ms]: Stimuli generated"\
                        .format((self.t_stim-self.t_start)*1000))

            # Run fixpoint simulation and return the results as integer values:
            self.fx_results = self.fx_wdg_inst.run_sim(
                dict_sig['fx_stimulus'])  # Run the simulation
            self.t_resp = time.process_time()

            if len(self.fx_results) == 0:
                logger.warning("Fixpoint simulation returned empty results!")
            else:
                #logger.debug("fx_results: {0}"\
                #            .format(pprint_log(self.fx_results, tab= " ")))
                logger.debug('Fixpoint simulation successful for dict\n{0}'
                         '\tStimuli: Shape {1} of type "{2}"'
                         '\n\tResponse: Shape {3} of type "{4}"'\
                           .format(pprint_log(dict_sig),
                                   np.shape(dict_sig['fx_stimulus']),
                                   dict_sig['fx_stimulus'].dtype,
                                   np.shape(self.fx_results),
                                   type(self.fx_results)
                                    ))
                logger.info('Fixpoint simulation [{0:5.3g} ms]: Response calculated'\
                            .format((self.t_resp - self.t_stim)*1000))

            #TODO: fixed point / integer to float conversion?
            #TODO: color push-button to show state of simulation
            #TODO: add QTimer single shot
#            self.timer_id = QtCore.QTimer()
#            self.timer_id.setSingleShot(True)
#            # kill simulation after some idle time, also add a button for this
#            self.timer_id.timeout.connect(self.kill_sim)

        except ValueError as e:
            logger.error("Simulator error {0}".format(e))
            self.fx_results = None
            qstyle_widget(self.butSimHDL, "error")
            self.sig_tx.emit({'sender': __name__, 'fx_sim': 'error'})
            return
        except AssertionError as e:
            logger.error('Fixpoint simulation failed for dict\n{0}'
                         '\twith msg. "{1}"\n\tStimuli: Shape {2} of type "{3}"'
                         '\n\tResponse: Shape {4} of type "{5}"'\
                           .format(pprint_log(dict_sig), e,
                                   np.shape(dict_sig['fx_stimulus']),
                                   dict_sig['fx_stimulus'].dtype,
                                   np.shape(self.fx_results),
                                   type(self.fx_results)
                                    ))

            self.fx_results = None
            qstyle_widget(self.butSimHDL, "error")
            self.sig_tx.emit({'sender': __name__, 'fx_sim': 'error'})
            return

        logger.debug("Sending fixpoint results")
        dict_sig = {
            'sender': __name__,
            'fx_sim': 'set_results',
            'fx_results': self.fx_results
        }
        self.sig_tx.emit(dict_sig)
        qstyle_widget(self.butSimHDL, "normal")
        return
Exemple #4
0
    def _construct_UI(self) -> None:
        """
        Intitialize the main GUI, consisting of:

        - A combo box to select the filter topology and an image of the topology

        - The input quantizer

        - The UI of the fixpoint filter widget

        - Simulation and export buttons
        """
# ------------------------------------------------------------------------------
        # Define frame and layout for the dynamically updated filter widget
        # The actual filter widget is instantiated in self.set_fixp_widget() later on

        self.layH_fx_wdg = QHBoxLayout()
        # self.layH_fx_wdg.setContentsMargins(*params['wdg_margins'])
        frmHDL_wdg = QFrame(self)
        frmHDL_wdg.setLayout(self.layH_fx_wdg)
        # frmHDL_wdg.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

# ------------------------------------------------------------------------------
#       Initialize fixpoint filter combobox, title and description
# ------------------------------------------------------------------------------
        self.cmb_fx_wdg = QComboBox(self)
        self.cmb_fx_wdg.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.lblTitle = QLabel("not set", self)
        self.lblTitle.setWordWrap(True)
        self.lblTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        layHTitle = QHBoxLayout()
        layHTitle.addWidget(self.cmb_fx_wdg)
        layHTitle.addWidget(self.lblTitle)

        self.frmTitle = QFrame(self)
        self.frmTitle.setLayout(layHTitle)
        self.frmTitle.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Input and Output Quantizer
# ------------------------------------------------------------------------------
#       - instantiate widgets for input and output quantizer
#       - pass the quantization (sub-?) dictionary to the constructor
# ------------------------------------------------------------------------------

        self.wdg_w_input = UI_W(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='w_input', label='', lock_visible=True)
        self.wdg_w_input.sig_tx.connect(self.process_sig_rx_local)

        cmb_q = ['round', 'floor', 'fix']

        self.wdg_w_output = UI_W(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='w_output', label='')
        self.wdg_w_output.sig_tx.connect(self.process_sig_rx_local)

        self.wdg_q_output = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QO'],
                                 wdg_name='q_output',
                                 label='Output Format <i>Q<sub>Y&nbsp;</sub></i>:',
                                 cmb_q=cmb_q, cmb_ov=['wrap', 'sat'])
        self.wdg_q_output.sig_tx.connect(self.sig_rx_local)

        if HAS_DS:
            cmb_q.append('dsm')
        self.wdg_q_input = UI_Q(self, q_dict=fb.fil[0]['fxqc']['QI'],
                                wdg_name='q_input',
                                label='Input Format <i>Q<sub>X&nbsp;</sub></i>:',
                                cmb_q=cmb_q)
        self.wdg_q_input.sig_tx.connect(self.sig_rx_local)

        # Layout and frame for input quantization
        layVQiWdg = QVBoxLayout()
        layVQiWdg.addWidget(self.wdg_q_input)
        layVQiWdg.addWidget(self.wdg_w_input)
        frmQiWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQiWdg.setLayout(layVQiWdg)
        frmQiWdg.setContentsMargins(*params['wdg_margins'])

        # Layout and frame for output quantization
        layVQoWdg = QVBoxLayout()
        layVQoWdg.addWidget(self.wdg_q_output)
        layVQoWdg.addWidget(self.wdg_w_output)
        frmQoWdg = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQoWdg.setLayout(layVQoWdg)
        frmQoWdg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Dynamically updated image of filter topology (label as placeholder)
# ------------------------------------------------------------------------------
        # allow setting background color
        # lbl_fixp_img_palette = QPalette()
        # lbl_fixp_img_palette.setColor(QPalette(window, Qt: white))
        # lbl_fixp_img_palette.setBrush(self.backgroundRole(), QColor(150, 0, 0))
        # lbl_fixp_img_palette.setColor(QPalette: WindowText, Qt: blue)

        self.lbl_fixp_img = QLabel("img not set", self)
        self.lbl_fixp_img.setAutoFillBackground(True)
        # self.lbl_fixp_img.setPalette(lbl_fixp_img_palette)
        # self.lbl_fixp_img.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.embed_fixp_img(self.no_fx_filter_img)
        layHImg = QHBoxLayout()
        layHImg.setContentsMargins(0, 0, 0, 0)
        layHImg.addWidget(self.lbl_fixp_img)  # , Qt.AlignCenter)
        self.frmImg = QFrame(self)
        self.frmImg.setLayout(layHImg)
        self.frmImg.setContentsMargins(*params['wdg_margins'])

# ------------------------------------------------------------------------------
#       Simulation and export Buttons
# ------------------------------------------------------------------------------
        self.butExportHDL = QPushButton(self)
        self.butExportHDL.setToolTip(
            "Create Verilog or VHDL netlist for fixpoint filter.")
        self.butExportHDL.setText("Create HDL")

        self.butSimFx = QPushButton(self)
        self.butSimFx.setToolTip("Start fixpoint simulation.")
        self.butSimFx.setText("Sim. FX")

        self.layHHdlBtns = QHBoxLayout()
        self.layHHdlBtns.addWidget(self.butSimFx)
        self.layHHdlBtns.addWidget(self.butExportHDL)
        # This frame encompasses the HDL buttons sim and convert
        frmHdlBtns = QFrame(self)
        # frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmHdlBtns.setLayout(self.layHHdlBtns)
        frmHdlBtns.setContentsMargins(*params['wdg_margins'])

# -------------------------------------------------------------------
#       Top level layout
# -------------------------------------------------------------------
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(frmHDL_wdg)
        splitter.addWidget(frmQoWdg)
        splitter.addWidget(self.frmImg)

        # setSizes uses absolute pixel values, but can be "misused" by specifying values
        # that are way too large: in this case, the space is distributed according
        # to the _ratio_ of the values:
        splitter.setSizes([3000, 3000, 5000])

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmTitle)
        layVMain.addWidget(frmHdlBtns)
        layVMain.addWidget(frmQiWdg)
        layVMain.addWidget(splitter)
        layVMain.addStretch()
        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        # ----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        self.sig_rx_local.connect(self.process_sig_rx_local)
        # dynamic connection in `self._update_fixp_widget()`:
        # -----
        # if hasattr(self.fx_filt_ui, "sig_rx"):
        #     self.sig_rx.connect(self.fx_filt_ui.sig_rx)
        # if hasattr(self.fx_filt_ui, "sig_tx"):
        #     self.fx_filt_ui.sig_tx.connect(self.sig_rx_local)
        # ----
        # ----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs
        # ----------------------------------------------------------------------
        self.cmb_fx_wdg.currentIndexChanged.connect(self._update_fixp_widget)
        self.butExportHDL.clicked.connect(self.exportHDL)
        self.butSimFx.clicked.connect(lambda x: self.emit({'fx_sim': 'start'}))
Exemple #5
0
    def _construct_UI(self):
        """
        Intitialize the main GUI, consisting of:
            
        - A combo box to select the filter topology and an image of the topology
        
        - The input quantizer
        
        - The UI of the fixpoint filter widget
        
        - Simulation and export buttons
        """
        #------------------------------------------------------------------------------
        # Define frame and layout for the dynamically updated filter widget
        # The actual filter widget is instantiated in self.set_fixp_widget() later on

        self.layH_fx_wdg = QHBoxLayout()
        #self.layH_fx_wdg.setContentsMargins(*params['wdg_margins'])
        frmHDL_wdg = QFrame(self)
        frmHDL_wdg.setLayout(self.layH_fx_wdg)
        #frmHDL_wdg.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        #------------------------------------------------------------------------------
        #       Initialize fixpoint filter combobox, title and description
        #------------------------------------------------------------------------------
        self.cmb_wdg_fixp = QComboBox(self)
        self.cmb_wdg_fixp.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.lblTitle = QLabel("not set", self)
        self.lblTitle.setWordWrap(True)
        self.lblTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        layHTitle = QHBoxLayout()
        layHTitle.addWidget(self.cmb_wdg_fixp)
        layHTitle.addWidget(self.lblTitle)

        self.frmTitle = QFrame(self)
        self.frmTitle.setLayout(layHTitle)
        self.frmTitle.setContentsMargins(*params['wdg_margins'])

        #------------------------------------------------------------------------------
        #       Input and Output Quantizer
        #------------------------------------------------------------------------------
        #       - instantiate widgets for input and output quantizer
        #       - pass the quantization (sub-?) dictionary to the constructor
        #------------------------------------------------------------------------------

        self.wdg_w_input = UI_W(self,
                                q_dict=fb.fil[0]['fxqc']['QI'],
                                id='w_input',
                                label='',
                                lock_visible=True)
        self.wdg_w_input.sig_tx.connect(self.process_sig_rx)

        cmb_q = ['round', 'floor', 'fix']

        self.wdg_w_output = UI_W(self,
                                 q_dict=fb.fil[0]['fxqc']['QO'],
                                 id='w_output',
                                 label='')
        self.wdg_w_output.sig_tx.connect(self.process_sig_rx)

        self.wdg_q_output = UI_Q(
            self,
            q_dict=fb.fil[0]['fxqc']['QO'],
            id='q_output',
            label='Output Format <i>Q<sub>Y&nbsp;</sub></i>:',
            cmb_q=cmb_q,
            cmb_ov=['wrap', 'sat'])
        self.wdg_q_output.sig_tx.connect(self.sig_rx)

        if HAS_DS:
            cmb_q.append('dsm')
        self.wdg_q_input = UI_Q(
            self,
            q_dict=fb.fil[0]['fxqc']['QI'],
            id='q_input',
            label='Input Format <i>Q<sub>X&nbsp;</sub></i>:',
            cmb_q=cmb_q)
        self.wdg_q_input.sig_tx.connect(self.sig_rx)

        # Layout and frame for input quantization
        layVQiWdg = QVBoxLayout()
        layVQiWdg.addWidget(self.wdg_q_input)
        layVQiWdg.addWidget(self.wdg_w_input)
        frmQiWdg = QFrame(self)
        #frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQiWdg.setLayout(layVQiWdg)
        frmQiWdg.setContentsMargins(*params['wdg_margins'])

        # Layout and frame for output quantization
        layVQoWdg = QVBoxLayout()
        layVQoWdg.addWidget(self.wdg_q_output)
        layVQoWdg.addWidget(self.wdg_w_output)
        frmQoWdg = QFrame(self)
        #frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmQoWdg.setLayout(layVQoWdg)
        frmQoWdg.setContentsMargins(*params['wdg_margins'])

        #------------------------------------------------------------------------------
        #       Dynamically updated image of filter topology
        #------------------------------------------------------------------------------
        # label is a placeholder for image
        self.lbl_fixp_img = QLabel("img not set", self)
        #self.lbl_fixp_img.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

        self.embed_fixp_img(self.no_fx_filter_img)

        layHImg = QHBoxLayout()
        layHImg.setContentsMargins(0, 0, 0, 0)
        layHImg.addWidget(self.lbl_fixp_img)  #, Qt.AlignCenter)
        self.frmImg = QFrame(self)
        self.frmImg.setLayout(layHImg)
        self.frmImg.setContentsMargins(*params['wdg_margins'])
        self.resize_img()
        #------------------------------------------------------------------------------
        #       Simulation and export Buttons
        #------------------------------------------------------------------------------
        self.butExportHDL = QPushButton(self)
        self.butExportHDL.setToolTip(
            "Export fixpoint filter in Verilog format.")
        self.butExportHDL.setText("Create HDL")

        self.butSimHDL = QPushButton(self)
        self.butSimHDL.setToolTip("Start migen fixpoint simulation.")
        self.butSimHDL.setText("Sim. HDL")

        self.butSimFxPy = QPushButton(self)
        self.butSimFxPy.setToolTip("Simulate filter with fixpoint effects.")
        self.butSimFxPy.setText("Sim. FixPy")

        self.layHHdlBtns = QHBoxLayout()
        self.layHHdlBtns.addWidget(self.butSimFxPy)
        self.layHHdlBtns.addWidget(self.butSimHDL)
        self.layHHdlBtns.addWidget(self.butExportHDL)
        # This frame encompasses the HDL buttons sim and convert
        frmHdlBtns = QFrame(self)
        #frmBtns.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
        frmHdlBtns.setLayout(self.layHHdlBtns)
        frmHdlBtns.setContentsMargins(*params['wdg_margins'])

        # -------------------------------------------------------------------
        #       Top level layout
        # -------------------------------------------------------------------
        splitter = QSplitter(self)
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(frmHDL_wdg)
        splitter.addWidget(frmQoWdg)
        splitter.addWidget(self.frmImg)

        # setSizes uses absolute pixel values, but can be "misused" by specifying values
        # that are way too large: in this case, the space is distributed according
        # to the _ratio_ of the values:
        splitter.setSizes([3000, 3000, 5000])

        layVMain = QVBoxLayout()
        layVMain.addWidget(self.frmTitle)
        layVMain.addWidget(frmHdlBtns)
        layVMain.addWidget(frmQiWdg)
        layVMain.addWidget(splitter)
        layVMain.addStretch()
        layVMain.setContentsMargins(*params['wdg_margins'])

        self.setLayout(layVMain)

        #----------------------------------------------------------------------
        # GLOBAL SIGNALS & SLOTs
        #----------------------------------------------------------------------
        self.sig_rx.connect(self.process_sig_rx)
        #----------------------------------------------------------------------
        # LOCAL SIGNALS & SLOTs & EVENTFILTERS
        #----------------------------------------------------------------------
        # monitor events and generate sig_resize event when resized
        self.lbl_fixp_img.installEventFilter(self)
        # ... then redraw image when resized
        self.sig_resize.connect(self.resize_img)

        self.cmb_wdg_fixp.currentIndexChanged.connect(self._update_fixp_widget)

        self.butExportHDL.clicked.connect(self.exportHDL)
        self.butSimHDL.clicked.connect(self.fx_sim_init)
        #----------------------------------------------------------------------
        inst_wdg_list = self._update_filter_cmb()
        if len(inst_wdg_list) == 0:
            logger.warning("No fixpoint filters found!")
        else:
            logger.debug("Imported {0:d} fixpoint filters:\n{1}".format(
                len(inst_wdg_list.split("\n")) - 1, inst_wdg_list))

        self._update_fixp_widget()