def setModelData(self, editor, model, index): """ When editor has finished, read the updated data from the editor, convert it back to floating point format and store it in both the model (= QTableWidget) and in self.ba. Finally, refresh the table item to display it in the selected format (via `float2frmt()`). editor: instance of e.g. QLineEdit model: instance of QAbstractTableModel index: instance of QModelIndex """ # check for different editor environments if needed and provide a default: # if isinstance(editor, QtGui.QTextEdit): # model.setData(index, editor.toPlainText()) # elif isinstance(editor, QComboBox): # model.setData(index, editor.currentText()) # else: # super(ItemDelegate, self).setModelData(editor, model, index) if self.parent.myQ.frmt == 'float': data = safe_eval(qstr(editor.text()), self.parent.ba[index.column()][index.row()], return_type='auto') # raw data without fixpoint formatting else: data = self.parent.myQ.frmt2float(qstr(editor.text()), self.parent.myQ.frmt) # transform back to float model.setData(index, data) # store in QTableWidget self.parent.ba[index.column()][index.row()] = data # and in self.ba qstyle_widget(self.parent.ui.butSave, 'changed') self.parent._refresh_table_item(index.row(), index.column()) # refresh table entry
def setModelData(self, editor, model, index): """ When editor has finished, read the updated data from the editor, convert it back to floating point format and store it in the model (= QTableWidget) and in self.ba editor: instance of e.g. QLineEdit model: instance of QAbstractTableModel index: instance of QModelIndex """ # check for different editor environments if needed and provide a default: # if isinstance(editor, QtGui.QTextEdit): # model.setData(index, editor.toPlainText()) # elif isinstance(editor, QComboBox): # model.setData(index, editor.currentText()) # else: # super(ItemDelegate, self).setModelData(editor, model, index) if self.parent.myQ.frmt == 'float': data = safe_eval( qstr(editor.text()), fb.data_old) # raw data without fixpoint formatting else: data = self.parent.myQ.frmt2float( qstr(editor.text()), self.parent.myQ.frmt) # transform back to float model.setData(index, data) # store in QTableWidget self.parent.ba[index.column()][index.row()] = data # and in self.ba qstyle_widget(self.parent.butSave, 'changed')
def setModelData(self, editor, model, index): """ When editor has finished, read the updated data from the editor, convert it back to floating point format and store it in both the model (= QTableWidget) and in self.ba. Finally, refresh the table item to display it in the selected format (via `float2frmt()`). editor: instance of e.g. QLineEdit model: instance of QAbstractTableModel index: instance of QModelIndex """ # check for different editor environments if needed and provide a default: # if isinstance(editor, QtGui.QTextEdit): # model.setData(index, editor.toPlainText()) # elif isinstance(editor, QComboBox): # model.setData(index, editor.currentText()) # else: # super(ItemDelegate, self).setModelData(editor, model, index) if self.parent.myQ.frmt == 'float': data = safe_eval(qstr(editor.text()), self.parent.ba[index.column()][index.row()], return_type='auto') # raw data without fixpoint formatting else: data = self.parent.myQ.frmt2float(qstr(editor.text()), self.parent.myQ.frmt) # transform back to float model.setData(index, data) # store in QTableWidget # if the entry is complex, convert ba (list of arrays) to complex type if isinstance(data, complex): self.parent.ba[0] = self.parent.ba[0].astype(complex) self.parent.ba[1] = self.parent.ba[1].astype(complex) self.parent.ba[index.column()][index.row()] = data # store in self.ba qstyle_widget(self.parent.ui.butSave, 'changed') self.parent._refresh_table_item(index.row(), index.column()) # refresh table entry
def _construct_UI(self, **kwargs): """ Construct widget """ dict_ui = {'label':'WI.WF', 'max_led_width':30, 'WI':0, 'WI_len':2, 'tip_WI':'Number of integer bits', 'WF':15,'WF_len':2, 'tip_WF':'Number of fractional bits', 'enabled':True, 'visible':True } for key, val in kwargs.items(): dict_ui.update({key:val}) # dict_ui.update(map(kwargs)) # same as above? self.WI = dict_ui['WI'] self.WF = dict_ui['WF'] lblW = QLabel(to_html(dict_ui['label'], frmt='bi'), self) self.ledWI = QLineEdit(self) self.ledWI.setToolTip(dict_ui['tip_WI']) self.ledWI.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits self.ledWI.setFixedWidth(dict_ui['max_led_width']) # width of lineedit in points(?) lblDot = QLabel(".", self) self.ledWF = QLineEdit(self) self.ledWF.setToolTip(dict_ui['tip_WF']) self.ledWF.setMaxLength(dict_ui['WI_len']) # maximum of 2 digits self.ledWF.setFixedWidth(dict_ui['max_led_width']) # width of lineedit in points(?) layH = QHBoxLayout() layH.addWidget(lblW) layH.addStretch() layH.addWidget(self.ledWI) layH.addWidget(lblDot) layH.addWidget(self.ledWF) layH.setContentsMargins(0,0,0,0) frmMain = QFrame(self) frmMain.setLayout(layH) layVMain = QVBoxLayout() # Widget main layout layVMain.addWidget(frmMain) layVMain.setContentsMargins(0,5,0,0)#*params['wdg_margins']) self.setLayout(layVMain) #---------------------------------------------------------------------- # INITIAL SETTINGS #---------------------------------------------------------------------- self.ledWI.setText(qstr(dict_ui['WI'])) self.ledWF.setText(qstr(dict_ui['WF'])) frmMain.setEnabled(dict_ui['enabled']) frmMain.setVisible(dict_ui['visible']) #---------------------------------------------------------------------- # LOCAL SIGNALS & SLOTs #---------------------------------------------------------------------- self.ledWI.editingFinished.connect(self.save_ui) self.ledWF.editingFinished.connect(self.save_ui)
def save_ui(self): """ Update the attributes `self.WI` and `self.WF` when one of the QLineEdit widgets has been edited. """ self.WI = safe_eval(self.ledWI.text(), self.WI, return_type="int", sign='pos') self.ledWI.setText(qstr(self.WI)) self.WF = safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='pos') self.ledWF.setText(qstr(self.WF))
def setupHDL(self, file_name="", dir_name=""): """ Setup instance of myHDL object with word lengths and coefficients """ self.qI_i = safe_eval(self.ledWIInput.text(), return_type='int', sign='pos') self.qF_i = safe_eval(self.ledWFInput.text(), return_type='int', sign='pos') self.ledWIInput.setText(qstr(self.qI_i)) self.ledWFInput.setText(qstr(self.qF_i)) self.qI_o = safe_eval(self.ledWIOutput.text(), return_type='int', sign='pos') self.qF_o = safe_eval(self.ledWFOutput.text(), return_type='int', sign='pos') self.ledWIOutput.setText(qstr(self.qI_o)) self.ledWFOutput.setText(qstr(self.qF_o)) qQuant_o = self.cmbQuant_o.currentText() qOvfl_o = self.cmbOvfl_o.currentText() q_obj_o = { 'WI': self.qI_o, 'WF': self.qF_o, 'quant': qQuant_o, 'ovfl': qOvfl_o } myQ_o = fix.Fixed(q_obj_o) # instantiate fixed-point object self.W = (self.qI_i + self.qF_i + 1, self.qF_i ) # Matlab format: (W,WF) # @todo: always use sos? The filter object is setup to always # @todo: generate a second order filter # get filter coefficients etc. from filter dict coeffs = fb.fil[0]['ba'] zpk = fb.fil[0]['zpk'] sos = fb.fil[0]['sos'] logger.info("W = {0}".format(self.W)) logger.info('b = {0}'.format(coeffs[0][0:3])) logger.info('a = {0}'.format(coeffs[1][0:3])) # =============== adapted from C. Felton's SIIR example ============= self.flt = FilterIIR( b=np.array(coeffs[0][0:3]), a=np.array(coeffs[1][0:3]), #sos = sos, doesn't work yet word_format=(self.W[0], 0, self.W[1])) self.flt.hdl_name = file_name self.flt.hdl_directory = dir_name
def save_ui(self): """ Update the attributes `self.WI` and `self.WF` and the filter dict when one of the QLineEdit widgets has been edited. """ self.WI = safe_eval(self.ledWI.text(), self.WI, return_type="int", sign='pos') self.ledWI.setText(qstr(self.WI)) self.WF = safe_eval(self.ledWF.text(), self.WF, return_type="int", sign='pos') self.ledWF.setText(qstr(self.WF)) fb.fil[0]["q_coeff"].update({'WI':self.WI, 'WF':self.WF})
def simFixPoint(self): """ Simulate filter in fix-point description """ # Setup the Testbench and run dlg = QFD(self) # instantiate file dialog object plt_types = "png (*.png);;svg (*.svg)" plt_dir_file, plt_type = dlg.getSaveFileName_(caption="Save plots as", directory=dirs.save_dir, filter=plt_types) plt_dir_file = qstr(plt_dir_file) # dir + file name without suffix if plt_dir_file != "": plt_type = extract_file_ext(qstr(plt_type)) # suffix plt_dir_file += plt_type[0] # file name with suffix plt_dir_file = os.path.normpath(plt_dir_file) # "sanitize" path plt_dir = os.path.dirname( plt_dir_file) # extract the directory path if not os.path.isdir( plt_dir): # create directory if it doesn't exist os.mkdir(plt_dir) dirs.save_dir = plt_dir # make this directory the new default / base dir plt_file = os.path.basename(plt_dir_file) logger.info('Creating plot file "{0}"'.format( os.path.join(plt_dir, plt_file))) self.setupHDL(file_name=plt_file, dir_name=plt_dir) logger.info("Fixpoint simulation setup") W = self.hdl_wdg_inst.W # Matlab format : W = (W_len,WF) tb = self.hdl_wdg_inst.flt.simulate_freqz(num_loops=3, Nfft=1024) clk = myhdl.Signal(False) ts = myhdl.Signal(False) x = myhdl.Signal( myhdl.intbv(0, min=-2**(W[0] - 1), max=2**(W[0] - 1))) y = myhdl.Signal( myhdl.intbv(0, min=-2**(W[0] - 1), max=2**(W[0] - 1))) try: sim = myhdl.Simulation(tb) logger.info("Fixpoint simulation started") sim.run() logger.info("Fixpoint plotting started") self.hdl_wdg_inst.flt.plot_response() logger.info("Fixpoint plotting finished") except myhdl.SimulationError as e: logger.warning("Simulation failed:\n{0}".format(e))
def load_ui(self): """ Update the ui and the attributes `self.WI` and `self.WF` from the filter dict. `load_ui()` has to be called when the coefficients or the word format has been changed outside the class, e.g. by a new filter design or by changing the coefficient format in `input_coeffs.py`. """ self.WI = fb.fil[0]['q_coeff']['WI'] self.WF = fb.fil[0]['q_coeff']['WF'] self.ledWI.setText(qstr(self.WI)) self.ledWF.setText(qstr(self.WF)) self.c_dict = build_coeff_dict()
def _store_q_settings(self): """ Read out the settings of the quantization comboboxes and store them in the filter dict. Update the fixpoint object. """ fb.fil[0]['q_coeff'] = { 'WI':safe_eval(self.ui.ledWI.text(), self.myQ.WI, return_type='int'), 'WF':safe_eval(self.ui.ledWF.text(), self.myQ.WF, return_type='int', sign='pos'), 'quant':qstr(self.ui.cmbQuant.currentText()), 'ovfl':qstr(self.ui.cmbQOvfl.currentText()), 'frmt':qstr(self.ui.cmbFormat.currentText()), 'scale':qstr(self.ui.ledScale.text()) } self._load_q_settings() # update widgets and the fixpoint object self.myQ
def setModelData(self, editor, model, index): """ When editor has finished, read the updated data from the editor, convert it to floating point format and store it in both the model (= QTableWidget) and in `zpk`. Finally, refresh the table item to display it in the selected format (via `to be defined`) and normalize the gain. editor: instance of e.g. QLineEdit model: instance of QAbstractTableModel index: instance of QModelIndex """ # check for different editor environments if needed and provide a default: # if isinstance(editor, QtGui.QTextEdit): # model.setData(index, editor.toPlainText()) # elif isinstance(editor, QComboBox): # model.setData(index, editor.currentText()) # else: # super(ItemDelegate, self).setModelData(editor, model, index) # convert entered string to complex, pass the old value as default data = self.parent.frmt2cmplx(qstr(editor.text()), self.parent.zpk[index.column()][index.row()]) model.setData(index, data) # store in QTableWidget self.parent.zpk[index.column()][index.row()] = data # and in self.ba qstyle_widget(self.parent.ui.butSave, 'changed') self.parent._refresh_table_item(index.row(), index.column()) # refresh table entry self.parent._normalize_gain() # recalculate gain
def setModelData(self, editor, model, index): """ When editor has finished, read the updated data from the editor, convert it to complex format and store it in both the model (= QTableWidget) and in `zpk`. Finally, refresh the table item to display it in the selected format (via `to be defined`) and normalize the gain. editor: instance of e.g. QLineEdit model: instance of QAbstractTableModel index: instance of QModelIndex """ # check for different editor environments if needed and provide a default: # if isinstance(editor, QtGui.QTextEdit): # model.setData(index, editor.toPlainText()) # elif isinstance(editor, QComboBox): # model.setData(index, editor.currentText()) # else: # super(ItemDelegate, self).setModelData(editor, model, index) # convert entered string to complex, pass the old value as default data = self.parent.frmt2cmplx( qstr(editor.text()), self.parent.zpk[index.column()][index.row()]) model.setData(index, data) # store in QTableWidget self.parent.zpk[index.column()][index.row()] = data # and in self.ba qstyle_widget(self.parent.ui.butSave, 'changed') self.parent._refresh_table_item(index.row(), index.column()) # refresh table entry self.parent._normalize_gain() # recalculate gain
def _store_q_settings(self): """ Read out the settings of the quantization comboboxes and store them in the filter dict. Update the fixpoint object and refresh table """ fb.fil[0]['q_coeff'] = { 'WI':safe_eval(self.ui.ledWI.text(), self.myQ.WI, return_type='int'), 'WF':safe_eval(self.ui.ledWF.text(), self.myQ.WF, return_type='int', sign='pos'), 'quant':qstr(self.ui.cmbQuant.currentText()), 'ovfl':qstr(self.ui.cmbQOvfl.currentText()), 'frmt':qstr(self.ui.cmbFormat.currentText()), 'scale':qstr(self.ui.ledScale.text()) } self.sig_tx.emit({'sender':__name__, 'view_changed':'q_coeff'}) self._load_q_settings() # update widgets and the fixpoint object self.myQ self._refresh_table()
def _load_q_settings(self): """ load the quantization settings from the filter dict and set the widgets accordingly. Update the fixpoint object. """ self.myQ.setQobj(fb.fil[0]['q_coeff']) q_coeff = self.myQ.q_obj self.ui.ledWI.setText(qstr(q_coeff['WI'])) self.ui.ledWF.setText(qstr(q_coeff['WF'])) qset_cmb_box(self.ui.cmbQuant, q_coeff['quant']) qset_cmb_box(self.ui.cmbQOvfl, q_coeff['ovfl']) qset_cmb_box(self.ui.cmbFormat, q_coeff['frmt']) self.ui.ledScale.setText(qstr(q_coeff['scale'])) self.ui.ledW.setText(qstr(self.myQ.W)) self.ui.lblLSB.setText("{0:.{1}g}".format(self.myQ.LSB, params['FMT_ba'])) self.ui.lblMSB.setText("{0:.{1}g}".format(self.myQ.MSB, params['FMT_ba'])) self.ui.lblMAX.setText("{0}".format(self.myQ.float2frmt(self.myQ.MAX/self.myQ.scale)))
def exportHDL(self): """ Synthesize HDL description of filter using myHDL module """ dlg = QFD(self) # instantiate file dialog object file_types = "Verilog (*.v);;VHDL (*.vhd)" hdl_file, hdl_filter = dlg.getSaveFileName_(caption="Save HDL as", directory=dirs.save_dir, filter=file_types) hdl_file = qstr(hdl_file) if hdl_file != "": # "operation cancelled" gives back an empty string hdl_file = os.path.normpath(hdl_file) hdl_type = extract_file_ext( qstr(hdl_filter))[0] # return '.v' or '.vhd' 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 # return the filename without suffix hdl_file_name = os.path.splitext(os.path.basename(hdl_file))[0] self.setupHDL(file_name=hdl_file_name, dir_name=hdl_dir_name) if str(hdl_type) == '.vhd': self.hdl_wdg_inst.flt.hdl_target = 'vhdl' suffix = '.vhd' else: self.hdl_wdg_inst.flt.hdl_target = 'verilog' suffix = '.v' logger.info('Creating hdl_file "{0}"'.format( os.path.join(hdl_dir_name, hdl_file_name + suffix))) self.hdl_wdg_inst.flt.convert() logger.info("HDL conversion finished!")
def cmplx2frmt(self, text, places=-1): """ Convert number "text" (real or complex or string) to the format defined by cmbPZFrmt. Returns: string """ # convert to "normal" string and prettify via safe_eval: data = safe_eval(qstr(text), return_type='auto') frmt = qget_cmb_box(self.ui.cmbPZFrmt) # get selected format if places == -1: full_prec = True else: full_prec = False if frmt == 'cartesian' or not (type(data) == complex): if full_prec: return "{0}".format(data) else: return "{0:.{plcs}g}".format(data, plcs=places) elif frmt == 'polar_rad': r, phi = np.absolute(data), np.angle(data, deg=False) if full_prec: return "{r} * {angle_char}{p} rad"\ .format(r=r, p=phi, angle_char=self.angle_char) else: return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g} rad"\ .format(r=r, p=phi, plcs=places, angle_char=self.angle_char) elif frmt == 'polar_deg': r, phi = np.absolute(data), np.angle(data, deg=True) if full_prec: return "{r} * {angle_char}{p}°"\ .format(r=r, p=phi, angle_char=self.angle_char) else: return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g}°"\ .format(r=r, p=phi, plcs=places, angle_char=self.angle_char) elif frmt == 'polar_pi': r, phi = np.absolute(data), np.angle(data, deg=False) / np.pi if full_prec: return "{r} * {angle_char}{p} pi"\ .format(r=r, p=phi, angle_char=self.angle_char) else: return "{r:.{plcs}g} * {angle_char}{p:.{plcs}g} pi"\ .format(r=r, p=phi, plcs=places, angle_char=self.angle_char) else: logger.error("Unknown format {0}.".format(frmt))
def displayText(self, text, locale): """ Display `text` with selected fixpoint base and number of places text: string / QVariant from QTableWidget to be rendered locale: locale for the text """ string = qstr(text) # convert to "normal" string if self.parent.myQ.frmt == 'float': data = safe_eval(string) return "{0:.{1}g}".format(data, params['FMT_ba']) else: return "{0:>{1}}".format(self.parent.myQ.float2frmt(string), self.parent.myQ.places)
def frmt2cmplx(self, text, default=0.): """ Convert format defined by cmbPZFrmt to real or complex """ conv_error = False text = qstr(text).replace( " ", "") # convert to "proper" string without blanks if qget_cmb_box(self.ui.cmbPZFrmt) == 'cartesian': return safe_eval(text, default, return_type='auto') else: polar_str = text.split('*' + self.angle_char, 1) if len(polar_str) < 2: # input is real or imaginary r = safe_eval(re.sub('[' + self.angle_char + '<∠°]', '', text), default, return_type='auto') x = r.real y = r.imag else: r = safe_eval(polar_str[0], sign='pos') if safe_eval.err > 0: conv_error = True if "°" in polar_str[1]: scale = np.pi / 180. # angle in degrees elif re.search('π$|pi$', polar_str[1]): scale = np.pi else: scale = 1. # angle in rad # remove right-most special characters (regex $) polar_str[1] = re.sub( '[' + self.angle_char + '<∠°π]$|rad$|pi$', '', polar_str[1]) phi = safe_eval(polar_str[1]) * scale if safe_eval.err > 0: conv_error = True if not conv_error: x = r * np.cos(phi) y = r * np.sin(phi) else: x = default.real y = default.imag logger.error( "Expression {0} could not be evaluated.".format(text)) return x + 1j * y
def displayText(self, text, locale): """ Display `text` with selected fixpoint base and number of places text: string / QVariant from QTableWidget to be rendered locale: locale for the text The instance parameter myQ.ovr_flag is set to +1 or -1 for positive / negative overflows, else it is 0. """ data_str = qstr(text) # convert to "normal" string if self.parent.myQ.frmt == 'float': data = safe_eval(data_str, return_type='auto') # convert to float return "{0:.{1}g}".format(data, params['FMT_ba']) else: return "{0:>{1}}".format(self.parent.myQ.float2frmt(data_str), self.parent.myQ.places)
def setEditorData(self, editor, index): """ Pass the data to be edited to the editor: - retrieve data with full accuracy from self.ba (in float format) - requantize data according to settings in fixpoint object - represent it in the selected format (int, hex, ...) editor: instance of e.g. QLineEdit index: instance of QModelIndex """ # data = qstr(index.data()) # get data from QTableWidget data_str = qstr(safe_eval(self.parent.ba[index.column()][index.row()], return_type="auto")) if self.parent.myQ.frmt == 'float': # floating point format: pass data with full resolution editor.setText(data_str) else: # fixpoint format with base: pass requantized data with required number of places editor.setText("{0:>{1}}".format(self.parent.myQ.float2frmt(data_str), self.parent.myQ.places))
def frmt2cmplx(self, text, default=0.): """ Convert format defined by cmbPZFrmt to real or complex """ conv_error = False text = qstr(text).replace(" ", "") # convert to "proper" string without blanks if qget_cmb_box(self.ui.cmbPZFrmt) == 'cartesian': return safe_eval(text, default, return_type='auto') else: polar_str = text.split('*' + self.angle_char, 1) if len(polar_str) < 2: # input is real or imaginary r = safe_eval(re.sub('['+self.angle_char+'<∠°]','', text), default, return_type='auto') x = r.real y = r.imag else: r = safe_eval(polar_str[0], sign='pos') if safe_eval.err > 0: conv_error = True if "°" in polar_str[1]: scale = np.pi / 180. # angle in degrees elif re.search('π$|pi$', polar_str[1]): scale = np.pi else: scale = 1. # angle in rad # remove right-most special characters (regex $) polar_str[1] = re.sub('['+self.angle_char+'<∠°π]$|rad$|pi$', '', polar_str[1]) phi = safe_eval(polar_str[1]) * scale if safe_eval.err > 0: conv_error = True if not conv_error: x = r * np.cos(phi) y = r * np.sin(phi) else: x = default.real y = default.imag logger.error("Expression {0} could not be evaluated.".format(text)) return x + 1j * y
def text(self, item): """ Return item text as string transformed by self.displayText() """ # return qstr(item.text()) # convert to "normal" string return qstr(self.displayText(item.text(), QtCore.QLocale()))
def displayText(self, text, locale): return "{:.{n_digits}g}".format(safe_eval(qstr(text), return_type='cmplx'), n_digits=params['FMT_pz'])
def frmt2float(self, y, frmt=None): """ Return floating point representation for fixpoint scalar `y` given in format `frmt`. - Construct string representation without radix point, count number of fractional places. - Calculate integer representation of string, taking the base into account (- When result is negative, calculate two's complement for `W` bits) - Scale with `2** -W` - Scale with the number of fractional places (depending on format!) Parameters ---------- y: scalar or string to be quantized with the numeric base specified by `frmt`. frmt: string (optional) any of the formats `float`, `dec`, `bin`, `hex`, `csd`) When `frmt` is unspecified, the instance parameter `self.frmt` is used Returns ------- floating point (`dtype=np.float64`) representation of fixpoint input. """ if y == "": return 0 if frmt is None: frmt = self.frmt frmt = frmt.lower() if frmt == 'float': # this handles floats, np scalars + arrays and strings / string arrays try: y_float = np.float64(y) except ValueError: try: y_float = np.complex(y).real except Exception as e: y_float = None logger.warning("Can't convert {0}: {1}".format(y, e)) return y_float else: # {'dec', 'bin', 'hex', 'csd'} # Find the number of places before the first radix point (if there is one) # and join integer and fractional parts # when returned string is empty, skip general conversions and rely on error handling # of individual routines # remove illegal characters and trailing zeros val_str = re.sub(self.FRMT_REGEX[frmt], r'', qstr(y)).lstrip('0') if len(val_str) > 0: val_str = val_str.replace( ',', '.') # ',' -> '.' for German-style numbers if val_str[ 0] == '.': # prepend '0' when the number starts with '.' val_str = '0' + val_str # TODO: This does not work with csd?! try: int_str, frc_str = val_str.split( '.') # split into integer and fractional places except ValueError: # no fractional part int_str = val_str frc_str = '' # count number of valid digits in string int_places = len(int_str) - 1 frc_places = len(frc_str) raw_str = val_str.replace( '.', '') # join integer and fractional part logger.debug("y={0}, val_str={1}, raw_str={2} ".format( y, val_str, raw_str)) else: return 0.0 # (1) calculate the decimal value of the input string using np.float64() # which takes the number of decimal places into account. # (2) divide by scale if frmt == 'dec': # try to convert string -> float directly with decimal point position try: y_float = self.fixp(val_str, scaling='div') except Exception as e: logger.warning(e) y_float = None elif frmt in {'hex', 'bin'}: # - Glue integer and fractional part to a string without radix point # - Divide by <base> ** <number of fractional places> for correct scaling # - Strip MSBs outside fixpoint range # - Transform numbers in negative 2's complement to negative floats. # - Calculate the fixpoint representation for correct saturation / quantization try: y_dec = int(raw_str, self.base) / self.base**frc_places if y_dec == 0: # avoid log2(0) return 0 int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0) # When number is outside fixpoint range, discard MSBs: if int_bits > self.WI + 1: if frmt == 'hex': raw_str = np.binary_repr(int(raw_str, 16)) raw_str = raw_str[int_bits - self.WI - 1:] # discard the upper bits y_dec = int(raw_str, 2) / self.base**frc_places # recalculate y_dec if y_dec == 0: # avoid log2(0) return 0 int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0) # ... and int_bits # now, y_dec is in the correct range: if int_bits <= self.WI: # positive number pass elif int_bits == self.WI + 1: # negative, calculate 2's complemente y_dec = y_dec - (1 << int_bits) # quantize / saturate / wrap & scale the integer value: y_float = self.fixp(y_dec, scaling='div') except Exception as e: logger.warning(e) y_dec = None y_float = None logger.debug("MSB={0} | LSB={1} | scale={2}".format( self.MSB, self.LSB, self.scale)) logger.debug("y_in={0} | y_dec={1}".format(y, y_dec)) elif frmt == 'csd': # - Glue integer and fractional part to a string without radix point # - Divide by 2 ** <number of fractional places> for correct scaling y_float = csd2dec(raw_str) if y_float is not None: y_float = y_float / 2**frc_places else: logger.error('Unknown output format "%s"!'.format(frmt)) y_float = None if frmt != "float": logger.debug("MSB={0:g} | scale={1:g} | " "y={2} | y_float={3}".format(self.MSB, self.scale, y, y_float)) if y_float is not None: return y_float else: return 0.0
def fixp(self, y, scaling='mult'): """ Return fixed-point integer or fractional representation for `y` (scalar or array-like) with the same shape as `y`. Saturation / two's complement wrapping happens outside the range +/- MSB, requantization (round, floor, fix, ...) is applied on the ratio `y / LSB`. Parameters ---------- y: scalar or array-like object input value (floating point format) to be quantized scaling: String Determine the scaling before and after quantizing / saturation *'mult'* float in, int out: `y` is multiplied by `self.scale` *before* quantizing / saturating **'div'**: int in, float out: `y` is divided by `self.scale` *after* quantizing / saturating. **'multdiv'**: float in, float out (default): both of the above For all other settings, `y` is transformed unscaled. Returns ------- float scalar or ndarray with the same shape as `y`, in the range `-2*self.MSB` ... `2*self.MSB-self.LSB` Examples: --------- >>> q_obj_a = {'WI':1, 'WF':6, 'ovfl':'sat', 'quant':'round'} >>> myQa = Fixed(q_obj_a) # instantiate fixed-point object myQa >>> myQa.resetN() # reset overflow counter >>> a = np.arange(0,5, 0.05) # create input signal >>> aq = myQa.fixed(a) # quantize input signal >>> plt.plot(a, aq) # plot quantized vs. original signal >>> print(myQa.N_over, "overflows!") # print number of overflows >>> # Convert output to same format as input: >>> b = np.arange(200, dtype = np.int16) >>> btype = np.result_type(b) >>> # MSB = 2**7, LSB = 2**2: >>> q_obj_b = {'WI':7, 'WF':-2, 'ovfl':'wrap', 'quant':'round'} >>> myQb = Fixed(q_obj_b) # instantiate fixed-point object myQb >>> bq = myQb.fixed(b) >>> bq = bq.astype(btype) # restore original variable type """ #====================================================================== # (1) : Convert input argument into proper floating point scalars / # arrays and initialize flags #====================================================================== if np.shape(y): # create empty arrays for result and overflows with same shape as y # for speedup, test for invalid types SCALAR = False y = np.asarray(y) # convert lists / tuples / ... to numpy arrays yq = np.zeros(y.shape) over_pos = over_neg = np.zeros(y.shape, dtype=bool) self.ovr_flag = np.zeros(y.shape, dtype=int) if np.issubdtype(y.dtype, np.number): pass elif y.dtype.kind in {'U', 'S'}: # string or unicode try: y = y.astype(np.float64) # try to convert to float except (TypeError, ValueError): try: np.char.replace(y, ' ', '') # remove all whitespace y = y.astype(complex) # try to convert to complex except (TypeError, ValueError ) as e: # try converting elements recursively y = list( map( lambda y_scalar: self.fixp(y_scalar, scaling=scaling), y)) else: logger.error("Argument '{0}' is of type '{1}',\n" "cannot convert to float.".format(y, y.dtype)) y = np.zeros(y.shape) else: SCALAR = True # get rid of errors that have occurred upstream if y is None or str(y) == "": y = 0 # If y is not a number, convert to string, remove whitespace and convert # to complex format: elif not np.issubdtype(type(y), np.number): y = qstr(y) y = y.replace(' ', '') # remove all whitespace try: y = float(y) except (TypeError, ValueError): try: y = complex(y) except (TypeError, ValueError) as e: logger.error("Argument '{0}' yields \n {1}".format( y, e)) y = 0.0 over_pos = over_neg = yq = 0 self.ovr_flag = 0 # convert pseudo-complex (imag = 0) and complex values to real y = np.real_if_close(y) if np.iscomplexobj(y): logger.warning( "Casting complex values to real before quantization!") # quantizing complex objects is not supported yet y = y.real scaling = scaling.lower() y_in = y # y before scaling / quantizing #====================================================================== # (2) : Multiply by `scale` factor before requantization and saturation # when `scaling=='mult'`or 'multdiv' #====================================================================== y = y / self.LSB if scaling in {'mult', 'multdiv'}: y = y * self.scale #====================================================================== # (3) : Divide by LSB and apply selected quantization method to convert # floating point inputs to "fixpoint integers" arrays # Next, multiply by LSB to restore original scale #===================================================================== if self.quant == 'floor': yq = np.floor(y) # largest integer i, such that i <= x (= binary truncation) elif self.quant == 'round': yq = np.round(y) # rounding, also = binary rounding elif self.quant == 'fix': yq = np.fix(y) # round to nearest integer towards zero ("Betragsschneiden") elif self.quant == 'ceil': yq = np.ceil(y) # smallest integer i, such that i >= x elif self.quant == 'rint': yq = np.rint(y) # round towards nearest int elif self.quant == 'none': yq = y # return unquantized value else: raise Exception('Unknown Requantization type "%s"!' % (self.quant)) yq = yq * self.LSB logger.debug("y_in={0} | y={1} | yq={2}".format(y_in, y, yq)) #====================================================================== # (4) : Handle Overflow / saturation in relation to MSB #===================================================================== if self.ovfl == 'none': pass else: # Bool. vectors with '1' for every neg./pos overflow: over_neg = (yq < self.MIN) over_pos = (yq > self.MAX) # create flag / array of flags for pos. / neg. overflows self.ovr_flag = over_pos.astype(int) - over_neg.astype(int) # No. of pos. / neg. / all overflows occured since last reset: self.N_over_neg += np.sum(over_neg) self.N_over_pos += np.sum(over_pos) self.N_over = self.N_over_neg + self.N_over_pos # Replace overflows with Min/Max-Values (saturation): if self.ovfl == 'sat': yq = np.where(over_pos, self.MAX, yq) # (cond, true, false) yq = np.where(over_neg, self.MIN, yq) # Replace overflows by two's complement wraparound (wrap) elif self.ovfl == 'wrap': yq = np.where( over_pos | over_neg, yq - 4. * self.MSB * np.fix( (np.sign(yq) * 2 * self.MSB + yq) / (4 * self.MSB)), yq) else: raise Exception('Unknown overflow type "%s"!' % (self.ovfl)) return None #====================================================================== # (5) : Divide result by `scale` factor when `scaling=='div'`or 'multdiv' # - frmt2float() # - filter_coeffs when quantizing the coefficients # float2frmt passes on the scaling argument #====================================================================== if scaling in {'div', 'multdiv'}: yq = yq / self.scale if SCALAR and isinstance(yq, np.ndarray): yq = yq.item() # convert singleton array to scalar return yq
def frmt2float(self, y, frmt=None): """ Return floating point representation for fixpoint scalar `y` given in format `frmt`. * When input format is float, return unchanged * else: - Remove illegal characters and leading '0's - Count number of fractional places `frc_places` and remove radix point - Calculate decimal, fractional representation `y_dec` of string, using the base and the number of fractional places (- Calculate two's complement for `W` bits for negative bin and hex numbers) - Calculate fixpoint float representation `y_float = fixp(y_dec, scaling='div')`, dividing the result by `scale`. Parameters ---------- y: scalar or string to be quantized with the numeric base specified by `frmt`. frmt: string (optional) any of the formats `float`, `dec`, `bin`, `hex`, `csd`) When `frmt` is unspecified, the instance parameter `self.frmt` is used Returns ------- quantized floating point (`dtype=np.float64`) representation of input string """ if y == "": return 0 if frmt is None: frmt = self.frmt frmt = frmt.lower() y_float = y_dec = None if frmt == 'float32': float_frmt = np.float32 elif frmt == 'float16': float_frmt = np.float16 if frmt == 'float': # this handles floats, np scalars + arrays and strings / string arrays try: y_float = np.float64(y) except ValueError: try: y_float = np.complex(y).real except Exception as e: y_float = None logger.warning("Can't convert {0}: {1}".format(y, e)) return y_float else: # {'dec', 'bin', 'hex', 'csd'} # Find the number of places before the first radix point (if there is one) # and join integer and fractional parts # when returned string is empty, skip general conversions and rely on error handling # of individual routines # remove illegal characters and trailing zeros val_str = re.sub(self.FRMT_REGEX[frmt], r'', qstr(y)).lstrip('0') if len(val_str) > 0: val_str = val_str.replace( ',', '.') # ',' -> '.' for German-style numbers if val_str[ 0] == '.': # prepend '0' when the number starts with '.' val_str = '0' + val_str # count number of fractional places in string try: _, frc_str = val_str.split( '.') # split into integer and fractional places frc_places = len(frc_str) except ValueError: # no fractional part frc_places = 0 raw_str = val_str.replace( '.', '') # join integer and fractional part logger.debug("y={0}, val_str={1}, raw_str={2} ".format( y, val_str, raw_str)) else: return 0.0 # (1) calculate the decimal value of the input string using np.float64() # which takes the number of decimal places into account. # (2) quantize and saturate # (3) divide by scale if frmt == 'dec': # try to convert string -> float directly with decimal point position try: y_dec = y_float = self.fixp(val_str, scaling='div') except Exception as e: logger.warning(e) elif frmt in {'hex', 'bin'}: # - Glue integer and fractional part to a string without radix point # - Check for a negative sign, use this information only in the end # - Divide by <base> ** <number of fractional places> for correct scaling # - Strip MSBs outside fixpoint range # - Transform numbers in negative 2's complement to negative floats. # - Calculate the fixpoint representation for correct saturation / quantization neg_sign = False try: if raw_str[0] == '-': neg_sign = True raw_str = raw_str.lstrip('-') y_dec = abs(int(raw_str, self.base) / self.base**frc_places) if y_dec == 0: # avoid log2(0) return 0 int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0) # When number is outside fixpoint range, discard MSBs: if int_bits > self.WI + 1: if frmt == 'hex': raw_str = np.binary_repr(int(raw_str, 16)) # discard the upper bits outside the valid range raw_str = raw_str[int_bits - self.WI - 1:] # recalculate y_dec for truncated string y_dec = int(raw_str, 2) / self.base**frc_places if y_dec == 0: # avoid log2(0) error in code below return 0 int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0) # ... and int_bits # now, y_dec is in the correct range: if int_bits <= self.WI: # positive number pass elif int_bits == self.WI + 1: # negative, calculate 2's complemente y_dec = y_dec - (1 << int_bits) # quantize / saturate / wrap & scale the integer value: if neg_sign: y_dec = -y_dec y_float = self.fixp(y_dec, scaling='div') except Exception as e: logger.warning(e) y_dec = y_float = None logger.debug("MSB={0} | LSB={1} | scale={2}".format( self.MSB, self.LSB, self.scale)) logger.debug("y_in={0} | y_dec={1}".format(y, y_dec)) # ---- elif frmt == 'csd': # - Glue integer and fractional part to a string without radix point # - Divide by 2 ** <number of fractional places> for correct scaling # - Calculate fixpoint representation for saturation / overflow effects y_dec = csd2dec_vec(raw_str) # csd -> integer if y_dec is not None: y_float = self.fixp(y_dec / 2**frc_places, scaling='div') # ---- else: logger.error('Unknown output format "%s"!'.format(frmt)) if frmt != "float": logger.debug("MSB={0:g} | scale={1:g} | raw_str={2} | val_str={3}"\ .format(self.MSB, self.scale, raw_str, val_str)) logger.debug("y={0} | y_dec = {1} | y_float={2}".format( y, y_dec, y_float)) if y_float is not None: return y_float else: return 0.0
def displayText(self, text, locale): return "{:.{n_digits}g}".format(safe_eval(qstr(text), return_type='cmplx'), n_digits = params['FMT_pz'])
def frmt2float(self, y, frmt=None): """ Return floating point representation for fixpoint scalar `y` given in format `frmt`. - Construct string representation without radix point, count number of fractional places. - Calculate integer representation of string, taking the base into account (- When result is negative, calculate two's complement for `W` bits) - Scale with `2** -W` - Scale with the number of fractional places (depending on format!) Parameters ---------- y: scalar or string to be quantized with the numeric base specified by `frmt`. frmt: string (optional) any of the formats `float`, `dec`, `bin`, `hex`, `csd`) When `frmt` is unspecified, the instance parameter `self.frmt` is used Returns ------- floating point (`dtype=np.float64`) representation of fixpoint input. """ if y == "": return 0 if frmt is None: frmt = self.frmt frmt = frmt.lower() if frmt == 'float': # this handles floats, np scalars + arrays and strings / string arrays try: y_float = np.float64(y) except ValueError: try: y_float = np.complex(y).real except Exception as e: y_float = None logger.warning("Can't convert {0}: {1}".format(y,e)) return y_float else: # {'dec', 'bin', 'hex', 'csd'} # Find the number of places before the first radix point (if there is one) # and join integer and fractional parts # when returned string is empty, skip general conversions and rely on error handling # of individual routines # remove illegal characters and trailing zeros val_str = re.sub(self.FRMT_REGEX[frmt],r'', qstr(y)).lstrip('0') if len(val_str) > 0: val_str = val_str.replace(',','.') # ',' -> '.' for German-style numbers if val_str[0] == '.': # prepend '0' when the number starts with '.' val_str = '0' + val_str # TODO: This does not work with csd?! try: int_str, frc_str = val_str.split('.') # split into integer and fractional places except ValueError: # no fractional part int_str = val_str frc_str = '' # count number of valid digits in string int_places = len(int_str)-1 frc_places = len(frc_str) raw_str = val_str.replace('.','') # join integer and fractional part logger.debug("y={0}, val_str={1}, raw_str={2} ".format(y, val_str, raw_str)) else: return 0.0 # (1) calculate the decimal value of the input string using np.float64() # which takes the number of decimal places into account. # (2) divide by scale if frmt == 'dec': # try to convert string -> float directly with decimal point position try: y_float = self.fixp(val_str, scaling='div') except Exception as e: logger.warning(e) y_float = None elif frmt in {'hex', 'bin'}: # - Glue integer and fractional part to a string without radix point # - Divide by <base> ** <number of fractional places> for correct scaling # - Strip MSBs outside fixpoint range # - Transform numbers in negative 2's complement to negative floats. # - Calculate the fixpoint representation for correct saturation / quantization try: y_dec = int(raw_str, self.base) / self.base**frc_places if y_dec == 0: # avoid log2(0) return 0 int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0) # When number is outside fixpoint range, discard MSBs: if int_bits > self.WI + 1: if frmt == 'hex': raw_str = np.binary_repr(int(raw_str, 16)) raw_str = raw_str[int_bits - self.WI - 1:] # discard the upper bits y_dec = int(raw_str, 2) / self.base**frc_places # recalculate y_dec if y_dec == 0: # avoid log2(0) return 0 int_bits = max(int(np.floor(np.log2(y_dec))) + 1, 0) # ... and int_bits # now, y_dec is in the correct range: if int_bits <= self.WI: # positive number pass elif int_bits == self.WI + 1: # negative, calculate 2's complemente y_dec = y_dec - (1 << int_bits) # quantize / saturate / wrap & scale the integer value: y_float = self.fixp(y_dec, scaling='div') except Exception as e: logger.warning(e) y_dec = None y_float = None logger.debug("MSB={0} | LSB={1} | scale={2}".format(self.MSB, self.LSB, self.scale)) logger.debug("y_in={0} | y_dec={1}".format(y, y_dec)) elif frmt == 'csd': # - Glue integer and fractional part to a string without radix point # - Divide by 2 ** <number of fractional places> for correct scaling y_float = csd2dec(raw_str) if y_float is not None: y_float = y_float / 2**frc_places else: logger.error('Unknown output format "%s"!'.format(frmt)) y_float = None if frmt != "float": logger.debug("MSB={0:g} | scale={1:g} | " "y={2} | y_float={3}".format(self.MSB, self.scale, y, y_float)) if y_float is not None: return y_float else: return 0.0
def fixp(self, y, scaling='mult'): """ Return fixed-point integer or fractional representation for `y` (scalar or array-like) with the same shape as `y`. Saturation / two's complement wrapping happens outside the range +/- MSB, requantization (round, floor, fix, ...) is applied on the ratio `y / LSB`. Parameters ---------- y: scalar or array-like object in floating point format to be quantized scaling: String When `scaling='mult'` (default), `y` is multiplied by `self.scale` before requantizing and saturating, when `scaling='div'`, `y` is divided by `self.scale`. For all other settings, `y` is transformed unscaled. Returns ------- float scalar or ndarray with the same shape as `y`, in the range `-2*self.MSB` ... `2*self.MSB-self.LSB` Examples: --------- >>> q_obj_a = {'WI':1, 'WF':6, 'ovfl':'sat', 'quant':'round'} >>> myQa = Fixed(q_obj_a) # instantiate fixed-point object myQa >>> myQa.resetN() # reset overflow counter >>> a = np.arange(0,5, 0.05) # create input signal >>> aq = myQa.fixed(a) # quantize input signal >>> plt.plot(a, aq) # plot quantized vs. original signal >>> print(myQa.N_over, "overflows!") # print number of overflows >>> # Convert output to same format as input: >>> b = np.arange(200, dtype = np.int16) >>> btype = np.result_type(b) >>> # MSB = 2**7, LSB = 2**2: >>> q_obj_b = {'WI':7, 'WF':-2, 'ovfl':'wrap', 'quant':'round'} >>> myQb = Fixed(q_obj_b) # instantiate fixed-point object myQb >>> bq = myQb.fixed(b) >>> bq = bq.astype(btype) # restore original variable type """ if np.shape(y): # create empty arrays for result and overflows with same shape as y # for speedup, test for invalid types SCALAR = False y = np.asarray(y) # convert lists / tuples / ... to numpy arrays yq = np.zeros(y.shape) over_pos = over_neg = np.zeros(y.shape, dtype = bool) self.ovr_flag = np.zeros(y.shape, dtype = int) if np.issubdtype(y.dtype, np.number): pass elif y.dtype.kind in {'U', 'S'}: # string or unicode try: y = y.astype(np.float64) # try to convert to float except (TypeError, ValueError): try: np.char.replace(y, ' ', '') # remove all whitespace y = y.astype(complex) # try to convert to complex except (TypeError, ValueError) as e: # try converting elements individually y = list(map(lambda y_scalar: self.fixp(y_scalar, scaling=scaling), y)) else: logger.error("Argument '{0}' is of type '{1}',\n" "cannot convert to float.".format(y, y.dtype)) y = np.zeros(y.shape) else: SCALAR = True # get rid of errors that have occurred upstream if y is None or str(y) == "": y = 0 # If y is not a number, convert to string, remove whitespace and convert # to complex format: elif not np.issubdtype(type(y), np.number): y = qstr(y) y = y.replace(' ','') # remove all whitespace try: y = float(y) except (TypeError, ValueError): try: y = complex(y) except (TypeError, ValueError) as e: logger.error("Argument '{0}' yields \n {1}".format(y,e)) y = 0.0 over_pos = over_neg = yq = 0 self.ovr_flag = 0 # convert pseudo-complex (imag = 0) and complex values to real y = np.real_if_close(y) if np.iscomplexobj(y): logger.warning("Casting complex values to real before quantization!") # quantizing complex objects is not supported yet y = y.real y_in = y # y before scaling # convert to "fixpoint integer" for requantizing in relation to LSB y = y / self.LSB if scaling == 'mult': y = y * self.scale if self.quant == 'floor': yq = np.floor(y) # largest integer i, such that i <= x (= binary truncation) elif self.quant == 'round': yq = np.round(y) # rounding, also = binary rounding elif self.quant == 'fix': yq = np.fix(y) # round to nearest integer towards zero ("Betragsschneiden") elif self.quant == 'ceil': yq = np.ceil(y) # smallest integer i, such that i >= x elif self.quant == 'rint': yq = np.rint(y) # round towards nearest int elif self.quant == 'none': yq = y # return unquantized value else: raise Exception('Unknown Requantization type "%s"!'%(self.quant)) # revert to original fractional scale yq = yq * self.LSB logger.debug("y_in={0} | y={1} | yq={2}".format(y_in, y, yq)) # Handle Overflow / saturation in relation to MSB if self.ovfl == 'none': pass else: # Bool. vectors with '1' for every neg./pos overflow: over_neg = (yq < self.MIN) over_pos = (yq > self.MAX) # create flag / array of flags for pos. / neg. overflows self.ovr_flag = over_pos.astype(int) - over_neg.astype(int) # No. of pos. / neg. / all overflows occured since last reset: self.N_over_neg += np.sum(over_neg) self.N_over_pos += np.sum(over_pos) self.N_over = self.N_over_neg + self.N_over_pos # Replace overflows with Min/Max-Values (saturation): if self.ovfl == 'sat': yq = np.where(over_pos, self.MAX, yq) # (cond, true, false) yq = np.where(over_neg, self.MIN, yq) # Replace overflows by two's complement wraparound (wrap) elif self.ovfl == 'wrap': yq = np.where(over_pos | over_neg, yq - 4. * self.MSB*np.fix((np.sign(yq) * 2 * self.MSB+yq)/(4*self.MSB)), yq) else: raise Exception('Unknown overflow type "%s"!'%(self.ovfl)) return None if scaling == 'div': yq = yq / self.scale if SCALAR and isinstance(yq, np.ndarray): yq = yq.item() # convert singleton array to scalar return yq