from pyradioconfig.calculator_model_framework.interfaces.icalculator import ICalculator
from pycalcmodel.core.variable import ModelVariableFormat, CreateModelVariableEnum
from enum import Enum

class calc_softmodem_sol(ICalculator):

    def buildVariables(self, model):
        self._addModelVariable(model, 'softmodem_modulation_type', Enum, ModelVariableFormat.DECIMAL, desc='Modulation type used for softmodem calculations')
        model.vars.softmodem_modulation_type.var_enum = CreateModelVariableEnum(
            enum_name='SoftmodemModulationTypeEnum',
            enum_desc='Softmodem Modulation Type',
            member_data=[
                ['NONE', 0, 'None'],
                ['SUN_OFDM', 1, 'SUN OFDM'],
                ['SUN_OQPSK', 2, 'SUN OQPSK'],
                ['SUN_FSK', 3, 'SUN FSK'],
                ['LEGACY_OQPSK', 4, 'Legacy OQPSK'],
                ['CW', 5, 'CW']
            ])

        self._addModelVariable(model, 'ofdm_interleaving_depth', Enum, ModelVariableFormat.DECIMAL, desc='Interleaving depth for SUN OFDM PHYs corresponding to PIB attribute phyOfdmInterleaving')
        model.vars.ofdm_interleaving_depth.var_enum = CreateModelVariableEnum(
            enum_name='OfdmInterleavingEnum',
            enum_desc='SUN OFDM Interleaving Depth',
            member_data=[
                ['ONE_SYMBOL', 0, 'One symbol'],
                ['SPREADING_FACTOR', 1, 'Number of symbols is equal to the frequency domain spreading factor']
            ])

        self._addModelVariable(model, 'fcs_type_802154', Enum, ModelVariableFormat.DECIMAL, desc='FCS type for 802154 PHYs')
        model.vars.fcs_type_802154.var_enum = CreateModelVariableEnum(
            enum_name='FcsTypeEnum',
            enum_desc='802154 FCS Type',
            member_data=[
                ['TWO_BYTE', 1, '16-bit ITU-T CRC'],
                ['FOUR_BYTE', 0, '32-bit ANSI X3.66-1979 CRC'],
            ])

        self._addModelVariable(model, 'sun_oqpsk_chiprate', Enum, ModelVariableFormat.DECIMAL, desc='Chiprate for SUN OQPSK PHYs')
        model.vars.sun_oqpsk_chiprate.var_enum = CreateModelVariableEnum(
            enum_name='SunOqpskChiprateEnum',
            enum_desc='SUN OQPSK Chiprate',
            member_data=[
                ['_100_KCPS', 0, '100 kcps'],
                ['_400_KCPS', 1, '400 kcps'],
                ['_1000_KCPS', 2, '1000 kcps'],
                ['_2000_KCPS', 3, '2000 kcps']
            ])

        self._addModelVariable(model, 'sun_fsk_fecsel', Enum, ModelVariableFormat.DECIMAL,
                               desc='FEC selection for SUN FSK PHYs')
        model.vars.sun_fsk_fecsel.var_enum = CreateModelVariableEnum(
            enum_name='SunFskFecSelEnum',
            enum_desc='SUN FSK FEC Selection',
            member_data=[
                ['NONE', 0, 'No FEC'],
                ['NRNSC_INTERLEAVING', 1, 'NRNSC interleaving'],
                ['RSC_NO_INTERLEAVING', 2, 'RSC no interleaving'],
                ['RSC_INTERLEAVING', 3, 'RSC interleaving']
            ])

        self._addModelVariable(model, 'sun_fsk_sfd', int, ModelVariableFormat.DECIMAL,
                               desc='SFD value for SUN FSK PHYs corresponding to PIB attribute phySunFskSfd')

        self._addModelVariable(model, 'cw_signal_type', Enum, ModelVariableFormat.DECIMAL,
                               desc='CW signal type selection')
        model.vars.cw_signal_type.var_enum = CreateModelVariableEnum(
            enum_name='CwSignalTypeEnum',
            enum_desc='CW signal type selection',
            member_data=[
                ['COMPLEX', 0, 'CW complex tone on I and Q'],
                ['I_ONLY', 1, 'CW real tone on I only (Q=0)'],
                ['Q_ONLY', 2, 'CW real tone on Q only (I=0)'],
                ['DUAL', 3, 'CW complex tone + its dual tone left shifted of 100 KHz']
            ])

        self._addModelVariable(model, 'cw_dual_path', Enum, ModelVariableFormat.DECIMAL,
                               desc='CW dual path selection')
        model.vars.cw_dual_path.var_enum = CreateModelVariableEnum(
            enum_name='CwDualPathEnum',
            enum_desc='CW dual path selection',
            member_data=[
                ['RXF1', 0, 'RXF1 single reception'],
                ['RXF1_RXF2', 1, 'RXF1 RXF2 dual reception']
            ])

        self._addModelVariable(model, 'cw_nfft_log2', int, ModelVariableFormat.DECIMAL,
                               desc='CW RX log2 of Nfft')

        self._addModelVariable(model, 'cw_fft_analyze', int, ModelVariableFormat.DECIMAL,
                               desc='CW fft analysis')
        model.vars.cw_fft_analyze.var_enum = CreateModelVariableEnum(
            enum_name='CwFftAnalysisEnum',
            enum_desc='CW fft analysis',
            member_data=[
                ['NONE', 0, 'FFT analysis not done'],
                ['ONE_BIN', 1, 'FFT analysis expects to find 1 single bin at CW Tone frequency value FREQ'],
                ['TWO_BINS', 2, 'FFT analysis expects to find 2 symmetrical bins at CW Tone frequency values +FREQ and -FREQ'],
                ['DUAL', 3, 'FFT anaysis expects to find 1 bin at FREQ + a 2nd bin left shifted of 100 KHz of FREQ'],
                ['LOOPBACK', 4, 'Specific FFT analysis mode for CW loopback sim']
            ])

        self._addModelVariable(model, 'cw_freq', int, ModelVariableFormat.DECIMAL,
                               desc='CW frequency for softmodem DAC')

        self._addModelVariable(model, 'cw_amp', int, ModelVariableFormat.DECIMAL,
                               desc='CW amplitude for softmodem DAC')

        self._addModelVariable(model, 'band_freq_mhz_802154', int, ModelVariableFormat.DECIMAL,
                               desc='Band frequency (in MHz) for SUN PHYs')

    def calc_softmodem_defaults(self, model):
        #This function assigns defaults to the softmodem vars so that other Profiles will correctly populate the vars
        model.vars.ofdm_option.value = model.vars.ofdm_option.var_enum.OPT1
        model.vars.sun_oqpsk_chiprate.value = model.vars.sun_oqpsk_chiprate.var_enum._100_KCPS

    def calc_softmodem_modulation_type(self, model):
        #For now, calculate the softmodem modulation type as None by default (must be manually set per softmodem PHY)
        #We can sometimes predict this based on the modulation type and demod selected

        #Read in model vars
        demod_select = model.vars.demod_select.value
        modulation_type = model.vars.modulation_type.value

        if demod_select == model.vars.demod_select.var_enum.SOFT_DEMOD:

            if modulation_type == model.vars.modulation_type.var_enum.OFDM:
                softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.SUN_OFDM
            elif modulation_type == model.vars.modulation_type.var_enum.OQPSK:
                #Assume SUN OQPSK for now, but need to either add code to predict LEGACY OQPSK or hard code this
                #Probably calculate this based on the Profile selected once all Profiles are complete
                softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.SUN_OQPSK
            elif modulation_type == model.vars.modulation_type.var_enum.FSK2:
                softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.SUN_FSK
            else:
                softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.NONE

        else:
            softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.NONE

        #Write the variable
        model.vars.softmodem_modulation_type.value = softmodem_modulation_type

    def calc_ofdm_interleaving_depth(self, model):
        #By default set the interleaving depth to 1 symbol

        model.vars.ofdm_interleaving_depth.value = model.vars.ofdm_interleaving_depth.var_enum.ONE_SYMBOL

    def calc_fcs_type_802154(self, model):

        #For the Wi-SUN Profiles, calc the CRC from the FCS that is specified as a Profile Input
        if 'sun' in model.profile.name.lower():
            fcs_type_802154 = model.vars.fcs_type_802154.value
            if fcs_type_802154 == model.vars.fcs_type_802154.var_enum.TWO_BYTE:
                crc_poly = model.vars.crc_poly.var_enum.CCITT_16
            else:
                crc_poly = model.vars.crc_poly.var_enum.ANSIX366_1979
            model.vars.crc_poly.value = crc_poly

        #For the other Profiles, calculate the FCS type variable from the CRC Profile Input
        else:
            crc_poly = model.vars.crc_poly.value
            if crc_poly == model.vars.crc_poly.var_enum.CCITT_16:
                fcs_type_802154 = model.vars.fcs_type_802154.var_enum.TWO_BYTE
            else:
                fcs_type_802154 = model.vars.fcs_type_802154.var_enum.FOUR_BYTE
            model.vars.fcs_type_802154.value = fcs_type_802154

    def calc_sun_fsk_fecsel(self, model):

        #Read in model variables
        fec_en = model.vars.fec_en.value

        #Translate between fec_en variable and the new FSK FEC variable
        if fec_en == model.vars.fec_en.var_enum.FEC_154G_NRNSC_INTERLEAVING:
            sun_fsk_fecsel = model.vars.sun_fsk_fecsel.var_enum.NRNSC_INTERLEAVING
        elif fec_en == model.vars.fec_en.var_enum.FEC_154G_RSC_INTERLEAVING:
            sun_fsk_fecsel = model.vars.sun_fsk_fecsel.var_enum.RSC_INTERLEAVING
        elif fec_en == model.vars.fec_en.var_enum.FEC_154G_RSC_NO_INTERLEAVING:
            sun_fsk_fecsel = model.vars.sun_fsk_fecsel.var_enum.RSC_NO_INTERLEAVING
        else:
            sun_fsk_fecsel = model.vars.sun_fsk_fecsel.var_enum.NONE

        #Write the variable
        model.vars.sun_fsk_fecsel.value = sun_fsk_fecsel

    def calc_sun_fsk_sfd(self, model):

        #For now, always set to 0 by default
        model.vars.sun_fsk_sfd.value = 0

    def calc_band_freq_mhz_802154(self, model):

        #Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        base_frequency_hz = model.vars.base_frequency_hz.value

        #From 802154-2020 Table 10-14—Channel numbering for SUN PHYs
        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.SUN_OQPSK:
            if base_frequency_hz < 779.2e6:
                band_freq_mhz_802154 = 470
            elif base_frequency_hz < 863.1e6:
                band_freq_mhz_802154 = 780
            elif base_frequency_hz <  870.1e6:
                band_freq_mhz_802154 = 868
            elif base_frequency_hz <  919.2e6:
                band_freq_mhz_802154 = 915
            elif base_frequency_hz < 920.6e6:
                band_freq_mhz_802154 = 917
            else:
                band_freq_mhz_802154 = 920

            #Write the model var
            model.vars.band_freq_mhz_802154.value = band_freq_mhz_802154

    def calc_softmodem_cw(self, model):

        #Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value
        dac_freq_actual = model.vars.dac_freq_actual.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.CW:

            model.vars.cw_amp.value = 24000  # Set default amplitude to 24000 (any forced value will override)
            model.vars.cw_nfft_log2.value = 9 # Set FFT size to 512
            model.vars.cw_fft_analyze.value = model.vars.cw_fft_analyze.var_enum.NONE

            do_not_care = False
            cw_amp = model.vars.cw_amp.value
            cw_freq = model.vars.cw_freq.value
            cw_dual_path = model.vars.cw_dual_path.value
            cw_signal_type = model.vars.cw_signal_type.value
            cw_nfft_log2 = model.vars.cw_nfft_log2.value
            cw_fft_analyze = model.vars.cw_fft_analyze.value
            softmodem_tx_interpolation1_actual = model.vars.TXFRONT_INT1CFG_RATIO.value + 1
            softmodem_tx_interpolation2_actual = model.vars.TXFRONT_INT2CFG_RATIO.value + 1

            #Copy modulation type into CW register
            modulation_field = softmodem_modulation_type

            #Calculate tone frequency
            # CW_tone_freq (Hz) = sampFreqHz * (freq/(2**16))
            freq_field = cw_freq / dac_freq_actual * softmodem_tx_interpolation1_actual * softmodem_tx_interpolation2_actual * (2**16)

            #Calculate dual path and signal type
            dual_path_field = cw_dual_path
            sig_type_field = cw_signal_type

            nfft_log2_field = cw_nfft_log2
            fft_analyze_field = cw_fft_analyze

            #Set digital amplitude to fullscale for now
            amp_field = cw_amp # CW tone amplitude [0:32767] - TX only - max 24000 to consider no saturation at TX FrontEnd output

            #Write crystal frequency
            xtal_freq_field = xtal_frequency_hz / 1000

        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            freq_field = 0
            amp_field = 0
            xtal_freq_field = 0
            nfft_log2_field = 0
            fft_analyze_field = 0
            dual_path_field = model.vars.cw_dual_path.var_enum.RXF1
            sig_type_field = model.vars.cw_signal_type.var_enum.COMPLEX

        #Note that tone amplitude is managed by RAIL firmware

        #Write the registers
        self._reg_write(model.vars.CW_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG1_NFFTLOG2, int(nfft_log2_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG1_DUALPATH, int(dual_path_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG1_FFTANALYZE, int(fft_analyze_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG1_SIGTYPE, int(sig_type_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG2_FREQ, int(freq_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG2_AMP, int(amp_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG3_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_sunofdm(self, model):

        # Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        ofdm_option = model.vars.ofdm_option.value
        ofdm_interleaving_depth = model.vars.ofdm_interleaving_depth.value
        fcs_type_802154 = model.vars.fcs_type_802154.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.SUN_OFDM:
            do_not_care = False

            # Copy modulation type into SUNOFDM register
            modulation_field = softmodem_modulation_type

            # Write OFDM option (the field is stored as option number minus one)
            ofdmoption_field = ofdm_option

            # Write interleaving PIB
            interleaving_field = ofdm_interleaving_depth

            # Write FCS type
            macfcstype_field = fcs_type_802154

            # Write crystal frequency
            xtal_freq_field = xtal_frequency_hz / 1000

        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            ofdmoption_field = 0
            interleaving_field = 0
            macfcstype_field = 0
            xtal_freq_field = 0

        #Write the registers
        self._reg_write(model.vars.SUNOFDM_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOFDM_CFG1_OFDMOPTION, int(ofdmoption_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOFDM_CFG1_INTERLEAVING, int(interleaving_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOFDM_CFG1_MACFCSTYPE, int(macfcstype_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOFDM_CFG1_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_sunoqpsk(self, model):

        # Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        base_frequency_hz = model.vars.base_frequency_hz.value
        fcs_type_802154 = model.vars.fcs_type_802154.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.SUN_OQPSK:
            do_not_care = False
            sun_oqpsk_chiprate = model.vars.sun_oqpsk_chiprate.value
            band_freq_mhz_802154 = model.vars.band_freq_mhz_802154.value

            # Copy modulation type into SUNOQPSK register
            modulation_field = softmodem_modulation_type

            #Write OQPSK chiprate
            chiprate_field = sun_oqpsk_chiprate

            #Write band freq
            bandfreqmhz_field = band_freq_mhz_802154

            # Write FCS type
            macfcstype_field = fcs_type_802154

            # Write crystal frequency
            xtal_freq_field = xtal_frequency_hz / 1000

        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            chiprate_field = 0
            bandfreqmhz_field = 0
            macfcstype_field = 0
            xtal_freq_field = 0

        #Write the registers
        self._reg_write(model.vars.SUNOQPSK_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOQPSK_CFG1_CHIPRATE, int(chiprate_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOQPSK_CFG1_BANDFREQMHZ, int(bandfreqmhz_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOQPSK_CFG2_MACFCSTYPE, int(macfcstype_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOQPSK_CFG2_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_legoqpsk(self, model):

        # Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        base_frequency_hz = model.vars.base_frequency_hz.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.LEGACY_OQPSK:
            do_not_care = False
            sun_oqpsk_chiprate = model.vars.sun_oqpsk_chiprate.value

            # Copy modulation type into LEGOQPSK register
            modulation_field = softmodem_modulation_type

            # Write OQPSK chiprate
            chiprate_field = sun_oqpsk_chiprate

            # Write band freq
            bandfreqmhz_field = int(base_frequency_hz / 1e6)

            # Write crystal frequency
            xtal_freq_field = xtal_frequency_hz / 1000

        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            chiprate_field = 0
            bandfreqmhz_field = 0
            xtal_freq_field = 0

        # Write the registers
        self._reg_write(model.vars.LEGOQPSK_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.LEGOQPSK_CFG1_CHIPRATE, int(chiprate_field), do_not_care=do_not_care)
        self._reg_write(model.vars.LEGOQPSK_CFG1_BANDFREQMHZ, int(bandfreqmhz_field), do_not_care=do_not_care)
        self._reg_write(model.vars.LEGOQPSK_CFG2_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_sunfsk(self, model):

        #Here we will copy results from many TRECS register calculations into the soft demod registers
        #At some point we will want to stop calculating hard demod registers and make them don't care
        #When we do this we will have to separate calculation of the field values and the reg writes themselves
        #by making additional model variables and then replacing register accesses here with those variables

        # Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        sun_fsk_fecsel = model.vars.sun_fsk_fecsel.value
        sun_fsk_sfd = model.vars.sun_fsk_sfd.value
        preamble_length = model.vars.preamble_length.value #This is the TX preamble length, ok for now
        modulation_index = model.vars.modulation_index.value
        trecsosr = model.vars.MODEM_TRECSCFG_TRECSOSR.value
        harddecision = model.vars.MODEM_VITERBIDEMOD_HARDDECISION.value
        ksi1 = model.vars.MODEM_VITERBIDEMOD_VITERBIKSI1.value
        ksi2 = model.vars.MODEM_VITERBIDEMOD_VITERBIKSI2.value
        ksi3 = model.vars.MODEM_VITERBIDEMOD_VITERBIKSI3.value
        ksi3w = model.vars.ksi3wb_actual.value
        phscale = model.vars.MODEM_TRECPMDET_PHSCALE.value
        pmmincostthd = model.vars.MODEM_TRECPMDET_PMMINCOSTTHD.value
        rtschwin = model.vars.MODEM_REALTIMCFE_RTSCHWIN.value
        pmcostvalthd = model.vars.MODEM_TRECPMDET_PMCOSTVALTHD.value
        preamsch_len = model.vars.preamsch_len.value
        errors_in_timing_window = model.vars.errors_in_timing_window.value
        pmtimeoutsel = model.vars.MODEM_TRECPMDET_PMTIMEOUTSEL.value
        vtfrqlim = model.vars.MODEM_VTCORRCFG1_VTFRQLIM.value
        pmtimlosthd = model.vars.MODEM_PHDMODCTRL_PMTIMLOSTHD.value
        syncword_0 = model.vars.syncword_0.value
        syncword_1 = model.vars.syncword_1.value
        syncword_length = model.vars.syncword_length.value
        mincostthd = model.vars.MODEM_REALTIMCFE_MINCOSTTHD.value
        frmschtime = model.vars.MODEM_FRMSCHTIME_FRMSCHTIME.value
        syncerrors = model.vars.MODEM_CTRL1_SYNCERRORS.value
        timgear = model.vars.MODEM_VTTRACK_TIMGEAR.value
        trackingwin = model.vars.MODEM_REALTIMCFE_TRACKINGWIN.value
        timtrackthd = model.vars.MODEM_VTTRACK_TIMTRACKTHD.value
        afc_scale = model.vars.afc_scale.value
        afcadjlim = model.vars.MODEM_AFCADJLIM_AFCADJLIM.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.SUN_FSK:
            #This access is inside the SUN_FSK case as shaping_filter_param is not defined for some Mbus PHYs
            shaping_filter_param = model.vars.shaping_filter_param.value

            do_not_care = False
            modulation_field = softmodem_modulation_type
            fecsel_field = sun_fsk_fecsel
            physunfsksfd_field = sun_fsk_sfd
            fskpreamblelength_field = int(preamble_length // 8) #This field is denoted in bytes
            modscheme_field = 0 #Set to 0 for 2FSK
            bt_field = 0 if (shaping_filter_param < 0.6) else 1
            modindex_field = int((modulation_index - 0.25)//0.5) # modulation index = 0.25 + (Modulation Index field value) × 0.05
            osr_field = trecsosr
            veqen_field = 1 if (harddecision == 0) else 0
            ksi1_field = ksi1
            ksi2_field = ksi2
            ksi3_field = ksi3
            ksi3w_field = int(ksi3w)
            phasescale_field = phscale
            preamblecostthd_field = pmmincostthd
            preamblecntwin_field = rtschwin
            preamblecntthd_field = pmcostvalthd
            preamblelen_field = preamsch_len #Use calculated preamble search length
            preambleerrors_field = errors_in_timing_window
            preambletimeout_field = 16 + 8*pmtimeoutsel #pmtimeoutsel value in symbols
            preamblepatt_field = 0x55555555 #Always alternating 01 for SUN FSK
            preamblefreqlim_field = vtfrqlim
            preamblecostmax_field = pmtimlosthd
            sfd1_field = syncword_0
            sfd2_field = syncword_1
            sfdlen_field = syncword_length
            sfdcostthd_field = mincostthd
            sfdtimeout_field = frmschtime
            sfderrors_field = syncerrors
            timtrackgear_field = timgear
            timtracksymb_field = (trackingwin + 1)  * 4
            timmintrans_field = timtrackthd
            realtimesearch_field = 1 #Always enable real time search for now
            nsymbbatch_field = 26 #Static number of symbols in batch for now
            chfiltswen_field = 1 #Enable BW narrowing (assume sufficient preamble for AFC)
            afcmode_field = 1 #digmix one-shot
            afcscale_field = int(afc_scale)
            afcadjlim_field = afcadjlim
            afcadjperiod_field = 1 #Hard coded for now
            afccostthd_field = pmcostvalthd
            xtal_freq_field = xtal_frequency_hz / 1000
        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            fecsel_field = model.vars.sun_fsk_fecsel.var_enum.NONE
            physunfsksfd_field = 0
            fskpreamblelength_field = 0
            modscheme_field = 0
            bt_field = 0
            modindex_field = 0
            osr_field = 0
            veqen_field = 0
            ksi1_field = 0
            ksi2_field = 0
            ksi3_field = 0
            ksi3w_field = 0
            phasescale_field = 0
            preamblecostthd_field = 0
            preamblecntwin_field = 0
            preamblecntthd_field = 0
            preamblelen_field = 0
            preambleerrors_field = 0
            preambletimeout_field = 0
            preamblepatt_field = 0
            preamblefreqlim_field = 0
            preamblecostmax_field = 0
            sfd1_field = 0
            sfd2_field = 0
            sfdlen_field = 0
            sfdcostthd_field = 0
            sfdtimeout_field = 0
            sfderrors_field = 0
            timtrackgear_field = 0
            timtracksymb_field = 0
            timmintrans_field = 0
            realtimesearch_field = 0
            nsymbbatch_field = 0
            chfiltswen_field = 0
            afcmode_field = 0
            afcscale_field = 0
            afcadjlim_field = 0
            afcadjperiod_field = 0
            afccostthd_field = 0
            xtal_freq_field = 0

        # Write the registers
        self._reg_write(model.vars.SUNFSK_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_FECSEL, int(fecsel_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_PHYSUNFSKSFD, int(physunfsksfd_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_FSKPREAMBLELENGTH, fskpreamblelength_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_MODSCHEME, modscheme_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_BT, bt_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_MODINDEX, modindex_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG3_OSR, osr_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG2_VEQEN, veqen_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG2_KSI1, ksi1_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG2_KSI2, ksi2_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG2_KSI3, ksi3_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG3_KSI3W, ksi3w_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG3_PHASESCALE, phasescale_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG4_PREAMBLECOSTTHD, preamblecostthd_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG4_PREAMBLECNTWIN, preamblecntwin_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG4_PREAMBLECNTTHD, preamblecntthd_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG5_PREAMBLELEN, preamblelen_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG5_PREAMBLEERRORS, preambleerrors_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG5_PREAMBLETIMEOUT, preambletimeout_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG6_PREAMBLEPATT, preamblepatt_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG7_PREAMBLEFREQLIM, preamblefreqlim_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG7_PREAMBLECOSTMAX, preamblecostmax_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG8_SFD1, sfd1_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG9_SFD2, sfd2_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG10_SFDLEN, sfdlen_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG10_SFDCOSTTHD, sfdcostthd_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG11_SFDTIMEOUT, sfdtimeout_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG11_SFDERRORS, sfderrors_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG11_TIMINGTRACKGEAR, timtrackgear_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG12_TIMINGTRACKSYMB, timtracksymb_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG12_TIMINGMINTRANS, timmintrans_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG12_REALTIMESEARCH, realtimesearch_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG12_NBSYMBBATCH, nsymbbatch_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG13_CHFILTERSWEN, chfiltswen_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG13_AFCMODE, afcmode_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG13_AFCSCALE, afcscale_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG14_AFCADJLIM, afcadjlim_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG15_AFCADJPERIOD, afcadjperiod_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG15_AFCCOSTTHD, afccostthd_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG15_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_clken(self, model):

        #Read in model vars
        demod_select = model.vars.demod_select.value

        #Need to set CLKEN=1 for RTL sims, but this is ignored in rail_scripts
        #Avoid disabling clock for hardmodem PHYs as this could impact LabATE testing
        if demod_select == model.vars.demod_select.var_enum.SOFT_DEMOD:
            self._reg_write(model.vars.RAC_SOFTMCTRL_CLKEN, 1)
        else:
            self._reg_do_not_care(model.vars.RAC_SOFTMCTRL_CLKEN)

from pyradioconfig.calculator_model_framework.interfaces.icalculator import ICalculator
from pycalcmodel.core.variable import ModelVariableFormat, CreateModelVariableEnum
from enum import Enum

class calc_softmodem_sol(ICalculator):

    def buildVariables(self, model):
        self._addModelVariable(model, 'softmodem_modulation_type', Enum, ModelVariableFormat.DECIMAL, desc='Modulation type used for softmodem calculations')
        model.vars.softmodem_modulation_type.var_enum = CreateModelVariableEnum(
            enum_name='SoftmodemModulationTypeEnum',
            enum_desc='Softmodem Modulation Type',
            member_data=[
                ['NONE', 0, 'None'],
                ['SUN_OFDM', 1, 'SUN OFDM'],
                ['SUN_OQPSK', 2, 'SUN OQPSK'],
                ['SUN_FSK', 3, 'SUN FSK'],
                ['LEGACY_OQPSK', 4, 'Legacy OQPSK'],
                ['CW', 5, 'CW']
            ])

        self._addModelVariable(model, 'ofdm_interleaving_depth', Enum, ModelVariableFormat.DECIMAL, desc='Interleaving depth for SUN OFDM PHYs corresponding to PIB attribute phyOfdmInterleaving')
        model.vars.ofdm_interleaving_depth.var_enum = CreateModelVariableEnum(
            enum_name='OfdmInterleavingEnum',
            enum_desc='SUN OFDM Interleaving Depth',
            member_data=[
                ['ONE_SYMBOL', 0, 'One symbol'],
                ['SPREADING_FACTOR', 1, 'Number of symbols is equal to the frequency domain spreading factor']
            ])

        self._addModelVariable(model, 'fcs_type_802154', Enum, ModelVariableFormat.DECIMAL, desc='FCS type for 802154 PHYs')
        model.vars.fcs_type_802154.var_enum = CreateModelVariableEnum(
            enum_name='FcsTypeEnum',
            enum_desc='802154 FCS Type',
            member_data=[
                ['TWO_BYTE', 1, '16-bit ITU-T CRC'],
                ['FOUR_BYTE', 0, '32-bit ANSI X3.66-1979 CRC'],
            ])

        self._addModelVariable(model, 'sun_oqpsk_chiprate', Enum, ModelVariableFormat.DECIMAL, desc='Chiprate for SUN OQPSK PHYs')
        model.vars.sun_oqpsk_chiprate.var_enum = CreateModelVariableEnum(
            enum_name='SunOqpskChiprateEnum',
            enum_desc='SUN OQPSK Chiprate',
            member_data=[
                ['_100_KCPS', 0, '100 kcps'],
                ['_400_KCPS', 1, '400 kcps'],
                ['_1000_KCPS', 2, '1000 kcps'],
                ['_2000_KCPS', 3, '2000 kcps']
            ])

        self._addModelVariable(model, 'sun_fsk_fecsel', Enum, ModelVariableFormat.DECIMAL,
                               desc='FEC selection for SUN FSK PHYs')
        model.vars.sun_fsk_fecsel.var_enum = CreateModelVariableEnum(
            enum_name='SunFskFecSelEnum',
            enum_desc='SUN FSK FEC Selection',
            member_data=[
                ['NONE', 0, 'No FEC'],
                ['NRNSC_INTERLEAVING', 1, 'NRNSC interleaving'],
                ['RSC_NO_INTERLEAVING', 2, 'RSC no interleaving'],
                ['RSC_INTERLEAVING', 3, 'RSC interleaving']
            ])

        self._addModelVariable(model, 'sun_fsk_sfd', int, ModelVariableFormat.DECIMAL,
                               desc='SFD value for SUN FSK PHYs corresponding to PIB attribute phySunFskSfd')

        self._addModelVariable(model, 'cw_signal_type', Enum, ModelVariableFormat.DECIMAL,
                               desc='CW signal type selection')
        model.vars.cw_signal_type.var_enum = CreateModelVariableEnum(
            enum_name='CwSignalTypeEnum',
            enum_desc='CW signal type selection',
            member_data=[
                ['COMPLEX', 0, 'CW complex tone on I and Q'],
                ['I_ONLY', 1, 'CW real tone on I only (Q=0)'],
                ['Q_ONLY', 2, 'CW real tone on Q only (I=0)'],
                ['DUAL', 3, 'CW complex tone + its dual tone left shifted of 100 KHz']
            ])

        self._addModelVariable(model, 'cw_dual_path', Enum, ModelVariableFormat.DECIMAL,
                               desc='CW dual path selection')
        model.vars.cw_dual_path.var_enum = CreateModelVariableEnum(
            enum_name='CwDualPathEnum',
            enum_desc='CW dual path selection',
            member_data=[
                ['RXF1', 0, 'RXF1 single reception'],
                ['RXF1_RXF2', 1, 'RXF1 RXF2 dual reception']
            ])

        self._addModelVariable(model, 'cw_nfft_log2', int, ModelVariableFormat.DECIMAL,
                               desc='CW RX log2 of Nfft')

        self._addModelVariable(model, 'cw_fft_analyze', int, ModelVariableFormat.DECIMAL,
                               desc='CW fft analysis')
        model.vars.cw_fft_analyze.var_enum = CreateModelVariableEnum(
            enum_name='CwFftAnalysisEnum',
            enum_desc='CW fft analysis',
            member_data=[
                ['NONE', 0, 'FFT analysis not done'],
                ['ONE_BIN', 1, 'FFT analysis expects to find 1 single bin at CW Tone frequency value FREQ'],
                ['TWO_BINS', 2, 'FFT analysis expects to find 2 symmetrical bins at CW Tone frequency values +FREQ and -FREQ'],
                ['DUAL', 3, 'FFT anaysis expects to find 1 bin at FREQ + a 2nd bin left shifted of 100 KHz of FREQ'],
                ['LOOPBACK', 4, 'Specific FFT analysis mode for CW loopback sim']
            ])

        self._addModelVariable(model, 'cw_freq', int, ModelVariableFormat.DECIMAL,
                               desc='CW frequency for softmodem DAC')

        self._addModelVariable(model, 'cw_amp', int, ModelVariableFormat.DECIMAL,
                               desc='CW amplitude for softmodem DAC')

        self._addModelVariable(model, 'band_freq_mhz_802154', int, ModelVariableFormat.DECIMAL,
                               desc='Band frequency (in MHz) for SUN PHYs')

    def calc_softmodem_defaults(self, model):
        #This function assigns defaults to the softmodem vars so that other Profiles will correctly populate the vars
        model.vars.ofdm_option.value = model.vars.ofdm_option.var_enum.OPT1
        model.vars.sun_oqpsk_chiprate.value = model.vars.sun_oqpsk_chiprate.var_enum._100_KCPS

    def calc_softmodem_modulation_type(self, model):
        #For now, calculate the softmodem modulation type as None by default (must be manually set per softmodem PHY)
        #We can sometimes predict this based on the modulation type and demod selected

        #Read in model vars
        demod_select = model.vars.demod_select.value
        modulation_type = model.vars.modulation_type.value

        if demod_select == model.vars.demod_select.var_enum.SOFT_DEMOD:

            if modulation_type == model.vars.modulation_type.var_enum.OFDM:
                softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.SUN_OFDM
            elif modulation_type == model.vars.modulation_type.var_enum.OQPSK:
                #Assume SUN OQPSK for now, but need to either add code to predict LEGACY OQPSK or hard code this
                #Probably calculate this based on the Profile selected once all Profiles are complete
                softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.SUN_OQPSK
            elif modulation_type == model.vars.modulation_type.var_enum.FSK2:
                softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.SUN_FSK
            else:
                softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.NONE

        else:
            softmodem_modulation_type = model.vars.softmodem_modulation_type.var_enum.NONE

        #Write the variable
        model.vars.softmodem_modulation_type.value = softmodem_modulation_type

    def calc_ofdm_interleaving_depth(self, model):
        #By default set the interleaving depth to 1 symbol

        model.vars.ofdm_interleaving_depth.value = model.vars.ofdm_interleaving_depth.var_enum.ONE_SYMBOL

    def calc_fcs_type_802154(self, model):

        #For the Wi-SUN Profiles, calc the CRC from the FCS that is specified as a Profile Input
        if 'sun' in model.profile.name.lower():
            fcs_type_802154 = model.vars.fcs_type_802154.value
            if fcs_type_802154 == model.vars.fcs_type_802154.var_enum.TWO_BYTE:
                crc_poly = model.vars.crc_poly.var_enum.CCITT_16
            else:
                crc_poly = model.vars.crc_poly.var_enum.ANSIX366_1979
            model.vars.crc_poly.value = crc_poly

        #For the other Profiles, calculate the FCS type variable from the CRC Profile Input
        else:
            crc_poly = model.vars.crc_poly.value
            if crc_poly == model.vars.crc_poly.var_enum.CCITT_16:
                fcs_type_802154 = model.vars.fcs_type_802154.var_enum.TWO_BYTE
            else:
                fcs_type_802154 = model.vars.fcs_type_802154.var_enum.FOUR_BYTE
            model.vars.fcs_type_802154.value = fcs_type_802154

    def calc_sun_fsk_fecsel(self, model):

        #Read in model variables
        fec_en = model.vars.fec_en.value

        #Translate between fec_en variable and the new FSK FEC variable
        if fec_en == model.vars.fec_en.var_enum.FEC_154G_NRNSC_INTERLEAVING:
            sun_fsk_fecsel = model.vars.sun_fsk_fecsel.var_enum.NRNSC_INTERLEAVING
        elif fec_en == model.vars.fec_en.var_enum.FEC_154G_RSC_INTERLEAVING:
            sun_fsk_fecsel = model.vars.sun_fsk_fecsel.var_enum.RSC_INTERLEAVING
        elif fec_en == model.vars.fec_en.var_enum.FEC_154G_RSC_NO_INTERLEAVING:
            sun_fsk_fecsel = model.vars.sun_fsk_fecsel.var_enum.RSC_NO_INTERLEAVING
        else:
            sun_fsk_fecsel = model.vars.sun_fsk_fecsel.var_enum.NONE

        #Write the variable
        model.vars.sun_fsk_fecsel.value = sun_fsk_fecsel

    def calc_sun_fsk_sfd(self, model):

        #For now, always set to 0 by default
        model.vars.sun_fsk_sfd.value = 0

    def calc_band_freq_mhz_802154(self, model):

        #Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        base_frequency_hz = model.vars.base_frequency_hz.value

        #From 802154-2020 Table 10-14—Channel numbering for SUN PHYs
        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.SUN_OQPSK:
            if base_frequency_hz < 779.2e6:
                band_freq_mhz_802154 = 470
            elif base_frequency_hz < 863.1e6:
                band_freq_mhz_802154 = 780
            elif base_frequency_hz <  870.1e6:
                band_freq_mhz_802154 = 868
            elif base_frequency_hz <  919.2e6:
                band_freq_mhz_802154 = 915
            elif base_frequency_hz < 920.6e6:
                band_freq_mhz_802154 = 917
            else:
                band_freq_mhz_802154 = 920

            #Write the model var
            model.vars.band_freq_mhz_802154.value = band_freq_mhz_802154

    def calc_softmodem_cw(self, model):

        #Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value
        dac_freq_actual = model.vars.dac_freq_actual.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.CW:

            model.vars.cw_amp.value = 24000  # Set default amplitude to 24000 (any forced value will override)
            model.vars.cw_nfft_log2.value = 9 # Set FFT size to 512
            model.vars.cw_fft_analyze.value = model.vars.cw_fft_analyze.var_enum.NONE

            do_not_care = False
            cw_amp = model.vars.cw_amp.value
            cw_freq = model.vars.cw_freq.value
            cw_dual_path = model.vars.cw_dual_path.value
            cw_signal_type = model.vars.cw_signal_type.value
            cw_nfft_log2 = model.vars.cw_nfft_log2.value
            cw_fft_analyze = model.vars.cw_fft_analyze.value
            softmodem_tx_interpolation1_actual = model.vars.TXFRONT_INT1CFG_RATIO.value + 1
            softmodem_tx_interpolation2_actual = model.vars.TXFRONT_INT2CFG_RATIO.value + 1

            #Copy modulation type into CW register
            modulation_field = softmodem_modulation_type

            #Calculate tone frequency
            # CW_tone_freq (Hz) = sampFreqHz * (freq/(2**16))
            freq_field = cw_freq / dac_freq_actual * softmodem_tx_interpolation1_actual * softmodem_tx_interpolation2_actual * (2**16)

            #Calculate dual path and signal type
            dual_path_field = cw_dual_path
            sig_type_field = cw_signal_type

            nfft_log2_field = cw_nfft_log2
            fft_analyze_field = cw_fft_analyze

            #Set digital amplitude to fullscale for now
            amp_field = cw_amp # CW tone amplitude [0:32767] - TX only - max 24000 to consider no saturation at TX FrontEnd output

            #Write crystal frequency
            xtal_freq_field = xtal_frequency_hz / 1000

        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            freq_field = 0
            amp_field = 0
            xtal_freq_field = 0
            nfft_log2_field = 0
            fft_analyze_field = 0
            dual_path_field = model.vars.cw_dual_path.var_enum.RXF1
            sig_type_field = model.vars.cw_signal_type.var_enum.COMPLEX

        #Note that tone amplitude is managed by RAIL firmware

        #Write the registers
        self._reg_write(model.vars.CW_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG1_NFFTLOG2, int(nfft_log2_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG1_DUALPATH, int(dual_path_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG1_FFTANALYZE, int(fft_analyze_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG1_SIGTYPE, int(sig_type_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG2_FREQ, int(freq_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG2_AMP, int(amp_field), do_not_care=do_not_care)
        self._reg_write(model.vars.CW_CFG3_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_sunofdm(self, model):

        # Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        ofdm_option = model.vars.ofdm_option.value
        ofdm_interleaving_depth = model.vars.ofdm_interleaving_depth.value
        fcs_type_802154 = model.vars.fcs_type_802154.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.SUN_OFDM:
            do_not_care = False

            # Copy modulation type into SUNOFDM register
            modulation_field = softmodem_modulation_type

            # Write OFDM option (the field is stored as option number minus one)
            ofdmoption_field = ofdm_option

            # Write interleaving PIB
            interleaving_field = ofdm_interleaving_depth

            # Write FCS type
            macfcstype_field = fcs_type_802154

            # Write crystal frequency
            xtal_freq_field = xtal_frequency_hz / 1000

        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            ofdmoption_field = 0
            interleaving_field = 0
            macfcstype_field = 0
            xtal_freq_field = 0

        #Write the registers
        self._reg_write(model.vars.SUNOFDM_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOFDM_CFG1_OFDMOPTION, int(ofdmoption_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOFDM_CFG1_INTERLEAVING, int(interleaving_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOFDM_CFG1_MACFCSTYPE, int(macfcstype_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOFDM_CFG1_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_sunoqpsk(self, model):

        # Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        base_frequency_hz = model.vars.base_frequency_hz.value
        fcs_type_802154 = model.vars.fcs_type_802154.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.SUN_OQPSK:
            do_not_care = False
            sun_oqpsk_chiprate = model.vars.sun_oqpsk_chiprate.value
            band_freq_mhz_802154 = model.vars.band_freq_mhz_802154.value

            # Copy modulation type into SUNOQPSK register
            modulation_field = softmodem_modulation_type

            #Write OQPSK chiprate
            chiprate_field = sun_oqpsk_chiprate

            #Write band freq
            bandfreqmhz_field = band_freq_mhz_802154

            # Write FCS type
            macfcstype_field = fcs_type_802154

            # Write crystal frequency
            xtal_freq_field = xtal_frequency_hz / 1000

        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            chiprate_field = 0
            bandfreqmhz_field = 0
            macfcstype_field = 0
            xtal_freq_field = 0

        #Write the registers
        self._reg_write(model.vars.SUNOQPSK_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOQPSK_CFG1_CHIPRATE, int(chiprate_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOQPSK_CFG1_BANDFREQMHZ, int(bandfreqmhz_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOQPSK_CFG2_MACFCSTYPE, int(macfcstype_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNOQPSK_CFG2_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_legoqpsk(self, model):

        # Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        base_frequency_hz = model.vars.base_frequency_hz.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.LEGACY_OQPSK:
            do_not_care = False
            sun_oqpsk_chiprate = model.vars.sun_oqpsk_chiprate.value

            # Copy modulation type into LEGOQPSK register
            modulation_field = softmodem_modulation_type

            # Write OQPSK chiprate
            chiprate_field = sun_oqpsk_chiprate

            # Write band freq
            bandfreqmhz_field = int(base_frequency_hz / 1e6)

            # Write crystal frequency
            xtal_freq_field = xtal_frequency_hz / 1000

        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            chiprate_field = 0
            bandfreqmhz_field = 0
            xtal_freq_field = 0

        # Write the registers
        self._reg_write(model.vars.LEGOQPSK_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.LEGOQPSK_CFG1_CHIPRATE, int(chiprate_field), do_not_care=do_not_care)
        self._reg_write(model.vars.LEGOQPSK_CFG1_BANDFREQMHZ, int(bandfreqmhz_field), do_not_care=do_not_care)
        self._reg_write(model.vars.LEGOQPSK_CFG2_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_sunfsk(self, model):

        #Here we will copy results from many TRECS register calculations into the soft demod registers
        #At some point we will want to stop calculating hard demod registers and make them don't care
        #When we do this we will have to separate calculation of the field values and the reg writes themselves
        #by making additional model variables and then replacing register accesses here with those variables

        # Read in model variables
        softmodem_modulation_type = model.vars.softmodem_modulation_type.value
        sun_fsk_fecsel = model.vars.sun_fsk_fecsel.value
        sun_fsk_sfd = model.vars.sun_fsk_sfd.value
        preamble_length = model.vars.preamble_length.value #This is the TX preamble length, ok for now
        modulation_index = model.vars.modulation_index.value
        trecsosr = model.vars.MODEM_TRECSCFG_TRECSOSR.value
        harddecision = model.vars.MODEM_VITERBIDEMOD_HARDDECISION.value
        ksi1 = model.vars.MODEM_VITERBIDEMOD_VITERBIKSI1.value
        ksi2 = model.vars.MODEM_VITERBIDEMOD_VITERBIKSI2.value
        ksi3 = model.vars.MODEM_VITERBIDEMOD_VITERBIKSI3.value
        ksi3w = model.vars.ksi3wb_actual.value
        phscale = model.vars.MODEM_TRECPMDET_PHSCALE.value
        pmmincostthd = model.vars.MODEM_TRECPMDET_PMMINCOSTTHD.value
        rtschwin = model.vars.MODEM_REALTIMCFE_RTSCHWIN.value
        pmcostvalthd = model.vars.MODEM_TRECPMDET_PMCOSTVALTHD.value
        preamsch_len = model.vars.preamsch_len.value
        errors_in_timing_window = model.vars.errors_in_timing_window.value
        pmtimeoutsel = model.vars.MODEM_TRECPMDET_PMTIMEOUTSEL.value
        vtfrqlim = model.vars.MODEM_VTCORRCFG1_VTFRQLIM.value
        pmtimlosthd = model.vars.MODEM_PHDMODCTRL_PMTIMLOSTHD.value
        syncword_0 = model.vars.syncword_0.value
        syncword_1 = model.vars.syncword_1.value
        syncword_length = model.vars.syncword_length.value
        mincostthd = model.vars.MODEM_REALTIMCFE_MINCOSTTHD.value
        frmschtime = model.vars.MODEM_FRMSCHTIME_FRMSCHTIME.value
        syncerrors = model.vars.MODEM_CTRL1_SYNCERRORS.value
        timgear = model.vars.MODEM_VTTRACK_TIMGEAR.value
        trackingwin = model.vars.MODEM_REALTIMCFE_TRACKINGWIN.value
        timtrackthd = model.vars.MODEM_VTTRACK_TIMTRACKTHD.value
        afc_scale = model.vars.afc_scale.value
        afcadjlim = model.vars.MODEM_AFCADJLIM_AFCADJLIM.value
        xtal_frequency_hz = model.vars.xtal_frequency_hz.value

        if softmodem_modulation_type == model.vars.softmodem_modulation_type.var_enum.SUN_FSK:
            #This access is inside the SUN_FSK case as shaping_filter_param is not defined for some Mbus PHYs
            shaping_filter_param = model.vars.shaping_filter_param.value

            do_not_care = False
            modulation_field = softmodem_modulation_type
            fecsel_field = sun_fsk_fecsel
            physunfsksfd_field = sun_fsk_sfd
            fskpreamblelength_field = int(preamble_length // 8) #This field is denoted in bytes
            modscheme_field = 0 #Set to 0 for 2FSK
            bt_field = 0 if (shaping_filter_param < 0.6) else 1
            modindex_field = int((modulation_index - 0.25)//0.5) # modulation index = 0.25 + (Modulation Index field value) × 0.05
            osr_field = trecsosr
            veqen_field = 1 if (harddecision == 0) else 0
            ksi1_field = ksi1
            ksi2_field = ksi2
            ksi3_field = ksi3
            ksi3w_field = int(ksi3w)
            phasescale_field = phscale
            preamblecostthd_field = pmmincostthd
            preamblecntwin_field = rtschwin
            preamblecntthd_field = pmcostvalthd
            preamblelen_field = preamsch_len #Use calculated preamble search length
            preambleerrors_field = errors_in_timing_window
            preambletimeout_field = 16 + 8*pmtimeoutsel #pmtimeoutsel value in symbols
            preamblepatt_field = 0x55555555 #Always alternating 01 for SUN FSK
            preamblefreqlim_field = vtfrqlim
            preamblecostmax_field = pmtimlosthd
            sfd1_field = syncword_0
            sfd2_field = syncword_1
            sfdlen_field = syncword_length
            sfdcostthd_field = mincostthd
            sfdtimeout_field = frmschtime
            sfderrors_field = syncerrors
            timtrackgear_field = timgear
            timtracksymb_field = (trackingwin + 1)  * 4
            timmintrans_field = timtrackthd
            realtimesearch_field = 1 #Always enable real time search for now
            nsymbbatch_field = 26 #Static number of symbols in batch for now
            chfiltswen_field = 1 #Enable BW narrowing (assume sufficient preamble for AFC)
            afcmode_field = 1 #digmix one-shot
            afcscale_field = int(afc_scale)
            afcadjlim_field = afcadjlim
            afcadjperiod_field = 1 #Hard coded for now
            afccostthd_field = pmcostvalthd
            xtal_freq_field = xtal_frequency_hz / 1000
        else:
            do_not_care = True
            modulation_field = model.vars.softmodem_modulation_type.var_enum.NONE
            fecsel_field = model.vars.sun_fsk_fecsel.var_enum.NONE
            physunfsksfd_field = 0
            fskpreamblelength_field = 0
            modscheme_field = 0
            bt_field = 0
            modindex_field = 0
            osr_field = 0
            veqen_field = 0
            ksi1_field = 0
            ksi2_field = 0
            ksi3_field = 0
            ksi3w_field = 0
            phasescale_field = 0
            preamblecostthd_field = 0
            preamblecntwin_field = 0
            preamblecntthd_field = 0
            preamblelen_field = 0
            preambleerrors_field = 0
            preambletimeout_field = 0
            preamblepatt_field = 0
            preamblefreqlim_field = 0
            preamblecostmax_field = 0
            sfd1_field = 0
            sfd2_field = 0
            sfdlen_field = 0
            sfdcostthd_field = 0
            sfdtimeout_field = 0
            sfderrors_field = 0
            timtrackgear_field = 0
            timtracksymb_field = 0
            timmintrans_field = 0
            realtimesearch_field = 0
            nsymbbatch_field = 0
            chfiltswen_field = 0
            afcmode_field = 0
            afcscale_field = 0
            afcadjlim_field = 0
            afcadjperiod_field = 0
            afccostthd_field = 0
            xtal_freq_field = 0

        # Write the registers
        self._reg_write(model.vars.SUNFSK_CFG1_MODULATION, int(modulation_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_FECSEL, int(fecsel_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_PHYSUNFSKSFD, int(physunfsksfd_field), do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_FSKPREAMBLELENGTH, fskpreamblelength_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_MODSCHEME, modscheme_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_BT, bt_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG1_MODINDEX, modindex_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG3_OSR, osr_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG2_VEQEN, veqen_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG2_KSI1, ksi1_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG2_KSI2, ksi2_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG2_KSI3, ksi3_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG3_KSI3W, ksi3w_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG3_PHASESCALE, phasescale_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG4_PREAMBLECOSTTHD, preamblecostthd_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG4_PREAMBLECNTWIN, preamblecntwin_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG4_PREAMBLECNTTHD, preamblecntthd_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG5_PREAMBLELEN, preamblelen_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG5_PREAMBLEERRORS, preambleerrors_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG5_PREAMBLETIMEOUT, preambletimeout_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG6_PREAMBLEPATT, preamblepatt_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG7_PREAMBLEFREQLIM, preamblefreqlim_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG7_PREAMBLECOSTMAX, preamblecostmax_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG8_SFD1, sfd1_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG9_SFD2, sfd2_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG10_SFDLEN, sfdlen_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG10_SFDCOSTTHD, sfdcostthd_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG11_SFDTIMEOUT, sfdtimeout_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG11_SFDERRORS, sfderrors_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG11_TIMINGTRACKGEAR, timtrackgear_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG12_TIMINGTRACKSYMB, timtracksymb_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG12_TIMINGMINTRANS, timmintrans_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG12_REALTIMESEARCH, realtimesearch_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG12_NBSYMBBATCH, nsymbbatch_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG13_CHFILTERSWEN, chfiltswen_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG13_AFCMODE, afcmode_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG13_AFCSCALE, afcscale_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG14_AFCADJLIM, afcadjlim_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG15_AFCADJPERIOD, afcadjperiod_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG15_AFCCOSTTHD, afccostthd_field, do_not_care=do_not_care)
        self._reg_write(model.vars.SUNFSK_CFG15_XTALFREQ, int(xtal_freq_field), do_not_care=do_not_care)

    def calc_softmodem_clken(self, model):

        #Read in model vars
        demod_select = model.vars.demod_select.value

        #Need to set CLKEN=1 for RTL sims, but this is ignored in rail_scripts
        #Avoid disabling clock for hardmodem PHYs as this could impact LabATE testing
        if demod_select == model.vars.demod_select.var_enum.SOFT_DEMOD:
            self._reg_write(model.vars.RAC_SOFTMCTRL_CLKEN, 1)
        else:
            self._reg_do_not_care(model.vars.RAC_SOFTMCTRL_CLKEN)

#This function assigns defaults to the softmodem vars so that other Profiles will correctly populate the vars #For now, calculate the softmodem modulation type as None by default (must be manually set per softmodem PHY) #We can sometimes predict this based on the modulation type and demod selected #Read in model vars #Assume SUN OQPSK for now, but need to either add code to predict LEGACY OQPSK or hard code this #Probably calculate this based on the Profile selected once all Profiles are complete #Write the variable #By default set the interleaving depth to 1 symbol #For the Wi-SUN Profiles, calc the CRC from the FCS that is specified as a Profile Input #For the other Profiles, calculate the FCS type variable from the CRC Profile Input #Read in model variables #Translate between fec_en variable and the new FSK FEC variable #Write the variable #For now, always set to 0 by default #Read in model variables #From 802154-2020 Table 10-14—Channel numbering for SUN PHYs #Write the model var #Read in model variables # Set default amplitude to 24000 (any forced value will override) # Set FFT size to 512 #Copy modulation type into CW register #Calculate tone frequency # CW_tone_freq (Hz) = sampFreqHz * (freq/(2**16)) #Calculate dual path and signal type #Set digital amplitude to fullscale for now # CW tone amplitude [0:32767] - TX only - max 24000 to consider no saturation at TX FrontEnd output #Write crystal frequency #Note that tone amplitude is managed by RAIL firmware #Write the registers # Read in model variables # Copy modulation type into SUNOFDM register # Write OFDM option (the field is stored as option number minus one) # Write interleaving PIB # Write FCS type # Write crystal frequency #Write the registers # Read in model variables # Copy modulation type into SUNOQPSK register #Write OQPSK chiprate #Write band freq # Write FCS type # Write crystal frequency #Write the registers # Read in model variables # Copy modulation type into LEGOQPSK register # Write OQPSK chiprate # Write band freq # Write crystal frequency # Write the registers #Here we will copy results from many TRECS register calculations into the soft demod registers #At some point we will want to stop calculating hard demod registers and make them don't care #When we do this we will have to separate calculation of the field values and the reg writes themselves #by making additional model variables and then replacing register accesses here with those variables # Read in model variables #This is the TX preamble length, ok for now #This access is inside the SUN_FSK case as shaping_filter_param is not defined for some Mbus PHYs #This field is denoted in bytes #Set to 0 for 2FSK # modulation index = 0.25 + (Modulation Index field value) × 0.05 #Use calculated preamble search length #pmtimeoutsel value in symbols #Always alternating 01 for SUN FSK #Always enable real time search for now #Static number of symbols in batch for now #Enable BW narrowing (assume sufficient preamble for AFC) #digmix one-shot #Hard coded for now # Write the registers #Read in model vars #Need to set CLKEN=1 for RTL sims, but this is ignored in rail_scripts #Avoid disabling clock for hardmodem PHYs as this could impact LabATE testing