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_)
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)
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)
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_)
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_)
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_)
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_)
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__}')
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]
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]