Exemplo n.º 1
0
    def __init__(self, value: Integral, width: Integral=None):
        # check input
        assert isinstance(value, Integral), f'{self.__class__.__name__} requires an integer value, but was given a {value.__class__.__name__}.'

        # determine constant format
        if width is None:
            format_ = SIntFormat.from_value(value)
        else:
            format_ = SIntFormat(width=width, min_val=value, max_val=value)

        # call the super constructor
        super().__init__(value=value, format_=format_)
Exemplo n.º 2
0
    def __init__(self, operand: ModelExpr, width=None):
        # create the output format
        if width is None:
            output_format = SIntFormat.from_values([operand.format_.min_val, operand.format_.max_val])
        else:
            output_format = SIntFormat(width=width)
            assert output_format.can_represent(operand.format_.min_val), \
                f'The given signed integer width {width} cannot represent the operand min value {operand.format.min_val}.'
            assert output_format.can_represent(operand.format_.max_val), \
                f'The given signed integer width {width} cannot represent the operand max value {operand.format.max_val}.'

        # call the super constructor
        super().__init__(operand=operand, output_format=output_format)
Exemplo n.º 3
0
    def __init__(self, operand: ModelExpr, width=None):
        # create the output format
        if width is None:
            # make sure we can handle this case
            assert isinstance(operand.format_.range_, Number), \
                f'The SInt width has to be specified in this case because the operand range is symbolic.  For reference, the operand range expression is {operand.format_.range_}.'

            # create the output format
            min_int_val = int(floor(-operand.format_.range_))
            max_int_val = int(ceil(operand.format_.range_))
            output_format = SIntFormat.from_values([min_int_val, max_int_val])
        else:
            output_format = SIntFormat(width=width)

        # call the superconstructor
        super().__init__(operand=operand, output_format=output_format)
Exemplo n.º 4
0
    def __init__(self, operand, shift: Integral):
        # wrap constant if needed
        operand = wrap_constant(operand)

        # make sure operand is an integer
        assert isinstance(operand.format_, (UIntFormat, SIntFormat)), \
               f'{self.__class__.__name__} only supports integer operands.'

        # compute parameters of the output format
        width   = self.compute_output_width(in_format=operand.format_, shift=shift)
        min_val = self.function(operand=operand.format_.min_val, shift=shift)
        max_val = self.function(operand=operand.format_.max_val, shift=shift)

        # create the output format
        if isinstance(operand.format_, UIntFormat):
            format_ = UIntFormat(width=width, min_val=min_val, max_val=max_val)
        elif isinstance(operand.format_, SIntFormat):
            format_ = SIntFormat(width=width, min_val=min_val, max_val=max_val)
        else:
            raise Exception('Unknown format type.')

        # save settings
        self.shift = shift

        # call the super constructor
        super().__init__(operand=operand, format_=format_)
Exemplo n.º 5
0
    def __init__(self, name, width=1, signed=False):
        # determine the foramt
        if signed:
            format_ = SIntFormat(width=width)
        else:
            format_ = UIntFormat(width=width)

        # call the super constructor
        super().__init__(name=name, format_=format_)
Exemplo n.º 6
0
    def __init__(self, clk=None, rst=None, cke=None,
                 seed=None, signed=False):
        # save settings
        self.clk = clk
        self.rst = rst
        self.cke = cke
        self.seed = seed

        # determine the output format
        if signed:
            format_ = SIntFormat(width=32)
        else:
            format_ = UIntFormat(width=32)

        # call the super constructor
        super().__init__(format_=format_)
Exemplo n.º 7
0
    def __init__(self, operand, key):
        # wrap constant if needed
        operand = wrap_constant(operand)

        # make sure operand is an integer
        assert isinstance(operand.format_, (UIntFormat, SIntFormat)), \
               f'{self.__class__.__name__} only supports integer operands.'

        # determine MSB and LSB of the slice
        if isinstance(key, Integral):
            msb = key
            lsb = key
        elif isinstance(key, slice):
            msb = key.start
            lsb = key.stop
        else:
            raise Exception(f'Unknown indexing type: {key.__class__.__name__}')

        # sanity checks for MSB
        assert isinstance(msb, Integral), 'MSB must be an integer.'
        assert 0 <= msb < operand.format_.width, f'MSB value out of range: {msb} (input width is {operand.format_.width})'

        # sanity checks for LSB
        assert isinstance(lsb, Integral), 'LSB must be an integer.'
        assert 0 <= lsb < operand.format_.width, f'LSB value out of range: {lsb} (input width is {operand.format_.width})'

        # sanity check for relative values of MSB and LSB
        assert lsb <= msb, 'LSB must be less than or equal to MSB.'

        # compute parameters of the output format
        width = msb - lsb + 1

        # create the output format
        if isinstance(operand.format_, UIntFormat):
            format_ = UIntFormat(width=width)
        elif isinstance(operand.format_, SIntFormat):
            format_ = SIntFormat(width=width)
        else:
            raise Exception('Unknown format type.')

        # save settings
        self.msb = msb
        self.lsb = lsb

        # call the super constructor
        super().__init__(operand=operand, format_=format_)
Exemplo n.º 8
0
def to_sint(operand, width=None):
    """
    Convert either an unsigned integer or real object *operand* to a signed integer object.

    :param operand: name of signal
    :param width:   specify signal width, in case a custom width is necessary
    :return:        signed integer object
    """
    if isinstance(operand.format_, RealFormat):
        return real_to_sint(operand=operand, width=width)
    elif isinstance(operand.format_, SIntFormat):
        # This is a kind of tricky case, even though it doesn't likely come up too often.  If the width is specified
        # and doesn't match that of the operand, then we have to return a version of the operand with the requested
        # width.  A deepcopy is used because this function is not supposed to mutate its arguments.

        if (width is not None) and (width != operand.format_.width):
            operand = deepcopy(operand)
            operand.format_ = SIntFormat(width=width, min_val=operand.format_.min_val, max_val=operand.format_.max_val)

        return operand
    elif isinstance(operand.format_, UIntFormat):
        return uint_to_sint(operand, width=width)
    else:
        raise Exception(f'Unknown format type: {operand.format.__class__.__name__}')
Exemplo n.º 9
0
    def __init__(self, filename=None, **system_values):
        # set a fixed random seed for repeatability
        np.random.seed(3)

        module_name = Path(filename).stem
        build_dir = Path(filename).parent

        #This is a wonky way of validating this.. :(
        assert (all([req_val in system_values for req_val in self.required_values()])), \
            f'Cannot build {module_name}, Missing parameter in config file'

        m = MixedSignalModel(module_name,
                             dt=system_values['dt'],
                             build_dir=build_dir,
                             real_type=get_dragonphy_real_type())

        # Random number generator seed (default generated with random.org)
        m.add_digital_input('noise_seed', width=32)

        # main I/O: input, output, and clock
        m.add_analog_input('in_')
        m.add_digital_output('out_sgn')
        m.add_digital_output('out_mag', width=system_values['n'])
        m.add_digital_input('clk_val')

        # emulator clock and reset
        m.add_digital_input('emu_clk')
        m.add_digital_input('emu_rst')

        # Noise controls
        m.add_analog_input('noise_rms')

        # determine when sampling should happen
        m.add_digital_state('clk_val_prev')
        m.set_next_cycle(m.clk_val_prev,
                         m.clk_val,
                         clk=m.emu_clk,
                         rst=m.emu_rst)

        # detect a rising edge on the clock
        m.bind_name('pos_edge', m.clk_val & (~m.clk_val_prev))

        # delay the positive edge signal
        m.add_digital_state('pos_edge_prev', init=0)
        m.set_next_cycle(m.pos_edge_prev,
                         m.pos_edge,
                         clk=m.emu_clk,
                         rst=m.emu_rst)

        # add noise
        sample_noise = m.set_gaussian_noise('sample_noise',
                                            std=m.noise_rms,
                                            clk=m.emu_clk,
                                            rst=m.emu_rst,
                                            lfsr_init=m.noise_seed)
        in_plus_noise = m.bind_name('in_plus_noise', m.in_ + sample_noise)

        # determine out_sgn (note that the definition is opposite of the typical
        # meaning; "0" means negative)
        out_sgn = if_(in_plus_noise < 0, 0, 1)
        m.set_next_cycle(m.out_sgn,
                         out_sgn,
                         clk=m.emu_clk,
                         rst=m.emu_rst,
                         ce=m.pos_edge_prev)

        # determine out_mag
        vref, n = system_values['vref'], system_values['n']
        abs_val = if_(in_plus_noise < 0, -1.0 * in_plus_noise, in_plus_noise)
        code_real_unclamped = (abs_val / vref) * ((2**(n - 1)) - 1)
        code_real = clamp_op(code_real_unclamped, 0, (2**(n - 1)) - 1)
        code_sint = to_sint(code_real, width=n + 1)

        # TODO: clean this up -- since real ranges are not intervals, we need to tell MSDSL
        # that the range of the signed integer is smaller
        code_sint.format_ = SIntFormat(width=n + 1,
                                       min_val=0,
                                       max_val=(2**(n - 1)) - 1)

        code_uint = to_uint(code_sint, width=n)
        m.set_next_cycle(m.out_mag,
                         code_uint,
                         clk=m.emu_clk,
                         rst=m.emu_rst,
                         ce=m.pos_edge_prev)

        # generate the model
        m.compile_to_file(VerilogGenerator())

        self.generated_files = [filename]
Exemplo n.º 10
0
    def __init__(self, filename=None, **system_values):
        # set a fixed random seed for repeatability
        np.random.seed(0)

        module_name = Path(filename).stem
        build_dir   = Path(filename).parent

        #This is a wonky way of validating this.. :(
        assert (all([req_val in system_values for req_val in self.required_values()])), \
            f'Cannot build {module_name}, Missing parameter in config file'

        m = MixedSignalModel(module_name, dt=system_values['dt'], build_dir=build_dir,
                             real_type=get_dragonphy_real_type())

        # Random number generator seeds (defaults generated with random.org)
        m.add_digital_input('jitter_seed', width=32)
        m.add_digital_input('noise_seed', width=32)

        # Chunk of bits from the history; corresponding delay is bit_idx/freq_tx delay
        m.add_digital_input('chunk', width=system_values['chunk_width'])
        m.add_digital_input('chunk_idx', width=int(ceil(log2(system_values['num_chunks']))))

        # Control code for the corresponding PI slice
        m.add_digital_input('pi_ctl', width=system_values['pi_ctl_width'])

        # Indicates sequencing of ADC slice within a bank (typically a static value)
        m.add_digital_input('slice_offset', width=int(ceil(log2(system_values['slices_per_bank']))))

        # Control codes that affect states in the slice
        m.add_digital_input('sample_ctl')
        m.add_digital_input('incr_sum')
        m.add_digital_input('write_output')

        # ADC sign and magnitude
        m.add_digital_output('out_sgn')
        m.add_digital_output('out_mag', width=system_values['n_adc'])

        # Emulator clock and reset
        m.add_digital_input('clk')
        m.add_digital_input('rst')

        # Noise controls
        m.add_analog_input('jitter_rms')
        m.add_analog_input('noise_rms')

        # Create "placeholder function" that can be updated
        # at runtime with the channel function
        chan_func = PlaceholderFunction(
                domain=system_values['func_domain'],
                order=system_values['func_order'],
                numel=system_values['func_numel'],
                coeff_widths=system_values['func_widths'],
                coeff_exps=system_values['func_exps']
        )

        # Check the function on a representative test case
        chan = Filter.from_file(get_file('build/chip_src/adapt_fir/chan.npy'))
        self.check_func_error(chan_func, chan.interp)

        # Add digital inputs that will be used to reconfigure the function at runtime
        wdata, waddr, we = add_placeholder_inputs(m=m, f=chan_func)

        # Sample the pi_ctl code
        m.add_digital_state('pi_ctl_sample', width=system_values['pi_ctl_width'])
        m.set_next_cycle(m.pi_ctl_sample, m.pi_ctl, clk=m.clk, rst=m.rst, ce=m.sample_ctl)

        # compute weights to apply to pulse responses
        weights = []
        for k in range(system_values['chunk_width']):
            # create a weight value for this bit
            weights.append(
                m.add_analog_state(
                    f'weights_{k}',
                    range_=system_values['vref_tx']
                )
            )

            # select a single bit from the chunk.  chunk_width=1 is unfortunately
            # a special case because some simulators don't support the bit-selection
            # syntax on a single-bit variable
            chunk_bit = m.chunk[k] if system_values['chunk_width'] > 1 else m.chunk

            # write the weight value
            m.set_next_cycle(
                weights[-1],
                if_(
                    chunk_bit,
                    system_values['vref_tx'],
                    -system_values['vref_tx']
                ),
                clk=m.clk,
                rst=m.rst
            )

        # Compute the delay due to the PI control code
        delay_amt_pre = m.bind_name(
            'delay_amt_pre',
            m.pi_ctl_sample / ((2.0**system_values['pi_ctl_width'])*system_values['freq_rx'])
        )

        # Add jitter to the sampling time
        if system_values['use_jitter']:
            # create a signal to represent jitter
            delay_amt_jitter = m.set_gaussian_noise(
                'delay_amt_jitter',
                std=m.jitter_rms,
                lfsr_init=m.jitter_seed,
                clk=m.clk,
                ce=m.sample_ctl,
                rst=m.rst
            )

            # add jitter to the delay amount (which might possibly yield a negative value)
            delay_amt_noisy = m.bind_name('delay_amt_noisy', delay_amt_pre + delay_amt_jitter)

            # make the delay amount non-negative
            delay_amt = m.bind_name('delay_amt', if_(delay_amt_noisy >= 0.0, delay_amt_noisy, 0.0))
        else:
            delay_amt = delay_amt_pre

        # Compute the delay due to the slice offset
        t_slice_offset = m.bind_name('t_slice_offset', m.slice_offset/system_values['freq_rx'])

        # Add the delay amount to the slice offset
        t_samp_new = m.bind_name('t_samp_new', t_slice_offset + delay_amt)

        # Determine if the new sampling time happens after the end of this period
        t_one_period = m.bind_name('t_one_period', system_values['slices_per_bank']/system_values['freq_rx'])
        exceeds_period = m.bind_name('exceeds_period', t_samp_new >= t_one_period)

        # Save the previous sample time
        t_samp_prev = m.add_analog_state('t_samp_prev', range_=system_values['slices_per_bank']/system_values['freq_rx'])
        m.set_next_cycle(t_samp_prev, t_samp_new-t_one_period, clk=m.clk, rst=m.rst, ce=m.sample_ctl)

        # Save whether the previous sample time exceeded one period
        prev_exceeded = m.add_digital_state('prev_exceeded')
        m.set_next_cycle(prev_exceeded, exceeds_period, clk=m.clk, rst=m.rst, ce=m.sample_ctl)

        # Compute the sample time to use for this period
        t_samp_idx = m.bind_name('t_samp_idx', concatenate([exceeds_period, prev_exceeded]))
        t_samp = m.bind_name(
            't_samp',
            array(
                [
                    t_samp_new,   # 0b00: exceeds_period=0, prev_exceeded=0
                    t_samp_new,   # 0b01: exceeds_period=0, prev_exceeded=1
                    0.0,          # 0b10: exceeds_period=1, prev_exceeded=0
                    t_samp_prev   # 0b11: exceeds_period=1, prev_exceeded=1
                ], t_samp_idx
            )
        )

        # Evaluate the step response function.  Note that the number of evaluation times is the
        # number of chunks plus one.
        f_eval = []
        for k in range(system_values['chunk_width']+1):
            # compute change time as an integer multiple of the TX period
            chg_idx = m.bind_name(
                f'chg_idx_{k}', (
                    system_values['slices_per_bank']*system_values['num_banks']
                    - (m.chunk_idx+1)*system_values['chunk_width']
                    + k
                )
            )

            # scale by TX period
            t_chg = m.bind_name(f't_chg_{k}', chg_idx/system_values['freq_tx'])

            # compute the kth evaluation time
            t_eval = m.bind_name(f't_eval_{k}', t_samp - t_chg)

            # evaluate the function (the last three inputs are used for updating the function contents)
            f_eval.append(m.set_from_sync_func(f'f_eval_{k}', chan_func, t_eval, clk=m.clk, rst=m.rst,
                                               wdata=wdata, waddr=waddr, we=we))

        # Compute the pulse responses for each bit
        pulse_resp = []
        for k in range(system_values['chunk_width']):
            pulse_resp.append(
                m.bind_name(
                    f'pulse_resp_{k}',
                    weights[k]*(f_eval[k] - f_eval[k+1])
                )
            )

        # sum up all of the pulse responses
        pulse_resp_sum = m.bind_name('pulse_resp_sum', sum_op(pulse_resp))

        # update the overall sample value
        sample_value_pre = m.add_analog_state('analog_sample_pre', range_=5*system_values['vref_rx'])
        m.set_next_cycle(
            sample_value_pre,
            if_(m.incr_sum, sample_value_pre + pulse_resp_sum, pulse_resp_sum),
            clk=m.clk,
            rst=m.rst
        )

        # add noise to the sample value
        if system_values['use_noise']:
            sample_noise = m.set_gaussian_noise(
                'sample_noise',
                std=m.noise_rms,
                clk=m.clk,
                rst=m.rst,
                ce=m.write_output,
                lfsr_init=m.noise_seed
            )
            sample_value = m.bind_name('sample_value', sample_value_pre + sample_noise)
        else:
            sample_value = sample_value_pre

        # there is a special case in which the output should not be updated:
        # when the previous cycle did not exceed the period, but this one did
        # in that case the sample value should be held constant
        should_write_output = m.bind_name('should_write_output',
                                          (prev_exceeded | (~exceeds_period)) & m.write_output)

        # determine out_sgn (note that the definition is opposite of the typical
        # meaning; "0" means negative)
        out_sgn = if_(sample_value < 0, 0, 1)
        m.set_next_cycle(m.out_sgn, out_sgn, clk=m.clk, rst=m.rst, ce=should_write_output)

        # determine out_mag
        vref_rx, n_adc = system_values['vref_rx'], system_values['n_adc']
        abs_val = m.bind_name('abs_val', if_(sample_value < 0, -1.0*sample_value, sample_value))
        code_real_unclamped = m.bind_name('code_real_unclamped', (abs_val / vref_rx) * ((2**(n_adc-1))-1))
        code_real = m.bind_name('code_real', clamp_op(code_real_unclamped, 0, (2**(n_adc-1))-1))

        # TODO: clean this up -- since real ranges are not intervals, we need to tell MSDSL
        # that the range of the signed integer is smaller
        code_sint = to_sint(code_real, width=n_adc+1)
        code_sint.format_ = SIntFormat(width=n_adc+1, min_val=0, max_val=(2**(n_adc-1))-1)
        code_sint = m.bind_name('code_sint', code_sint)

        code_uint = m.bind_name('code_uint', to_uint(code_sint, width=n_adc))
        m.set_next_cycle(m.out_mag, code_uint, clk=m.clk, rst=m.rst, ce=should_write_output)

        # generate the model
        m.compile_to_file(VerilogGenerator())

        self.generated_files = [filename]