def diode(self, p, n, r_on=1, r_off=1e9, vf=0.9): # internal node x = self.tmp_var_name() # diode on/off control signal ctl = self.model.add_digital_state(self.tmp_var_name()) # compute forward voltage vf_name = self.tmp_var_name() vf_signal = self.model.bind_name(vf_name, if_(ctl, vf * (1 - r_on / r_off), 0)) # model topology curr = self.voltage(p=p, n=x, value=vf_signal) self.switch(p=x, n=n, ctl=ctl, r_on=r_on, r_off=r_off) # set up circuit monitoring volt = AnalogSignal(p) - AnalogSignal(n) self.extra_outputs += [curr, AnalogSignal(p), AnalogSignal(n)] # logic to determine when the diode is on or off DIODE_OFF = 0 DIODE_ON = 1 self.model.set_next_cycle( ctl, if_(ctl == DIODE_OFF, if_(volt > 0, DIODE_ON, DIODE_OFF), if_(curr < 0, DIODE_OFF, DIODE_ON)))
def __init__(self, filename=None, **system_values): # set a fixed random seed for repeatability np.random.seed(2) module_name = Path(filename).stem build_dir = Path(filename).parent print(f'Running model generator for {module_name}...') 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()) m.add_real_param('t_del', 0) m.add_digital_input('emu_rst') m.add_digital_input('emu_clk') m.add_analog_input('t_lo') m.add_analog_input('t_hi') m.add_analog_input('emu_dt') m.add_analog_output('dt_req', init=m.t_del) m.add_digital_output('clk_val') # determine if the request was granted m.bind_name('req_grant', m.dt_req == m.emu_dt) # update the clock value m.add_digital_state('prev_clk_val') m.set_next_cycle(m.prev_clk_val, m.clk_val, clk=m.emu_clk, rst=m.emu_rst) m.set_this_cycle(m.clk_val, if_(m.req_grant, ~m.prev_clk_val, m.prev_clk_val)) # determine arguments for formating time steps # TODO: clean this up dt_fmt_kwargs = dict( range_=m.emu_dt.format_.range_, width=m.emu_dt.format_.width, exponent=m.emu_dt.format_.exponent ) array_fmt_kwargs = deepcopy(dt_fmt_kwargs) array_fmt_kwargs['real_range_hint'] = m.emu_dt.format_.range_ del array_fmt_kwargs['range_'] # determine the next period dt_req_next_array = array([m.t_hi, m.t_lo], m.prev_clk_val, **array_fmt_kwargs) m.bind_name('dt_req_next', dt_req_next_array, **dt_fmt_kwargs) # increment the time request m.bind_name('dt_req_incr', m.dt_req - m.emu_dt, **dt_fmt_kwargs) # determine the next period dt_req_imm_array = array([m.dt_req_incr, m.dt_req_next], m.req_grant, **array_fmt_kwargs) m.bind_name('dt_req_imm', dt_req_imm_array, **dt_fmt_kwargs) m.set_next_cycle(m.dt_req, m.dt_req_imm, clk=m.emu_clk, rst=m.emu_rst) # generate the model m.compile_to_file(VerilogGenerator()) self.generated_files = [filename]
def main(): print('Running model generator...') # parse command line arguments parser = ArgumentParser() parser.add_argument('-o', '--output', type=str, default='build') parser.add_argument('--dt', type=float, default=0.1e-6) a = parser.parse_args() # create the model m = MixedSignalModel('osc', dt=a.dt) m.add_digital_input('emu_clk') m.add_digital_input('emu_rst') m.add_digital_output('dt_req', 32) m.add_digital_input('emu_dt', 32) m.add_digital_input('neg_emu_dt', 32) # TODO: cleanup m.add_digital_output('clk_val') m.add_digital_input('t_lo', 32) m.add_digital_input('t_hi', 32) # determine if the request was granted m.bind_name('req_grant', m.dt_req == m.emu_dt) # update the clock value m.add_digital_state('prev_clk_val') m.set_next_cycle(m.prev_clk_val, m.clk_val, clk=m.emu_clk, rst=m.emu_rst) m.set_this_cycle(m.clk_val, if_(m.req_grant, ~m.prev_clk_val, m.prev_clk_val)) # determine the next period m.bind_name('dt_req_next', if_(m.prev_clk_val, m.t_lo, m.t_hi)) # increment the time request m.bind_name('dt_req_incr', m.dt_req + m.neg_emu_dt) # determine the next period m.bind_name('dt_req_imm', if_(m.req_grant, m.dt_req_next, m.dt_req_incr)) m.set_next_cycle(m.dt_req, m.dt_req_imm[31:0], clk=m.emu_clk, rst=m.emu_rst) # determine the output filename filename = Path(a.output).resolve() / f'{m.module_name}.sv' print(f'Model will be written to: {filename}') # generate the model m.compile_to_file(VerilogGenerator(), filename)
def inertial_delay(self, input_: ModelExpr, tr: Number, tf: Number): """ Applies a resource-efficient implementation of a long delay for *one-bit digital signals only*. Note that the pulse width of the input expression should be wider than the delay, otherwise the signal will be filtered. :param input_: Expression that should be delayed. Must evaluate to a one-bit signal. :param tr: Rising edge delay, in seconds. :param tf: Falling edge delay, in seconds. :return: Object representing the delayed signal, which should be assigned to another signal using immediate_assign. """ # input type checking assert isinstance(input_.format_, IntFormat) and input_.format_.width == 1, \ 'Inertial delay only supports one-bit signals at this time.' # determine number of cycles to delay tr_int = int(round(tr / self.dt)) tf_int = int(round(tf / self.dt)) # determine counter width width = int(ceil(log2(1 + max(tr_int, tf_int)))) # determine base name basename = input_.name if hasattr(input_, 'name') else next(self.namer) # create output and counter variable in_ = self.bind_name(basename + '_in', input_) count = self.add_digital_state(name=basename + '_count', width=width, signed=False) out = self.add_digital_state(name=basename + '_out', width=1, signed=False) target = if_(in_, tr_int, tf_int) done = self.bind_name(basename + '_done', (out == in_) | (count == target)) self.set_next_cycle(out, if_(done, in_, out)) self.set_next_cycle(count, if_(done, 0, (count + 1)[(width - 1):0])) return out
def __init__(self, filename=None, **system_values): # set a fixed random seed for repeatability np.random.seed(4) 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()) m.add_digital_input('in_') m.add_analog_output('out') m.add_digital_input('cke') m.add_digital_input('clk') m.add_digital_input('rst') # save previous value of cke m.add_digital_state('cke_prev', init=0) m.set_next_cycle(m.cke_prev, m.cke, clk=m.clk, rst=m.rst) # detect positive edge of cke m.add_digital_signal('cke_posedge') m.set_this_cycle(m.cke_posedge, m.cke & (~m.cke_prev)) # define model behavior vp, vn = system_values['vp'], system_values['vn'] m.set_next_cycle(m.out, if_(m.in_, vp, vn), clk=m.clk, rst=m.rst, ce=m.cke_posedge) # 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(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, t_step, v_step, dtmax, num_spline=4, num_terms=50, func_order=1, func_numel=512, in_='in_', out_prefix='out', dt='dt', clk=None, rst=None, ce=None, out_range=None, **kwargs): # call the super constructor super().__init__(**kwargs) # define IOs in_ = self.add_analog_input(in_) outputs = [] for i in range(num_spline): outputs.append(self.add_analog_output(f'{out_prefix}_{i}')) dt = self.add_analog_input(dt) if clk is not None: clk = self.add_digital_input(clk) if rst is not None: rst = self.add_digital_input(rst) if ce is not None: ce = self.add_digital_input(ce) # calculate the output range if needed if out_range is None: out_range = self.calc_out_range( t_step=t_step, v_step=v_step, in_range=[-1, 1], dt=dtmax/(num_spline-1), num_terms=((num_spline-1)*num_terms)+1) self.out_range = out_range # create an interpolator for the step response chan_interp_base = interp1d( t_step, v_step, bounds_error=False, fill_value=(v_step[0], v_step[-1])) # generate a list of functions that evaluate the # step response at various offsets chan_interp_funs = [] for i in range(num_spline): chan_interp_funs.append( lambda t, i=i: chan_interp_base(t + (i/(num_spline-1))*dtmax)) # create the single-input, multi-output step response function chan_func = self.make_function( chan_interp_funs, name=f'chan_func', domain=[t_step[0], t_step[-1]], order=func_order, numel=func_numel ) # create a history of past inputs new_v = self.add_digital_signal('new_v') value_hist = self.make_history(in_, num_terms+1, clk=clk, rst=rst, ce=new_v) self.set_this_cycle(new_v, value_hist[0] != value_hist[1]) # create a history times in the past when the input changed time_incr = [] time_mux = [] for j in range(num_terms+1): if j == 0: time_incr.append(dt) time_mux.append(0) else: # create the signal mem_sig = AnalogState(name=f'time_mem_{j}', range_=t_step[-1]*1.01, init=0.0) self.add_signal(mem_sig) # increment time by dt_sig (this is the output from the current tap) # note that incrementing is clamped so that it doesn't exceed the range of mem_sig incr_sig = self.bind_name(f'time_incr_{j}', clamp_op(mem_sig+dt, 0, t_step[-1])) time_incr.append(incr_sig) # mux input of DFF between current and previous memory value mux_sig = self.bind_name(f'time_mux_{j}', if_(new_v, time_incr[j-1], time_incr[j])) time_mux.append(mux_sig) # delayed assignment self.set_next_cycle(signal=mem_sig, expr=mux_sig, clk=clk, rst=rst) # evaluate the step response function step = [] for j in range(num_terms+1): # generate names for the step response evaluations names = [f'step_{j}_{i}' for i in range(num_spline)] # evaluate the step response function step.append( self.set_from_sync_func( names, chan_func, time_mux[j], clk=clk, rst=rst)) # loop over all output points for i in range(num_spline): # build up list of step & pulse responses prod = [] # compute the products to be summed for j in range(num_terms+1): if j == 0: prod_sig = self.bind_name(f'prod_{i}_{j}', value_hist[j]*step[j][i]) else: prod_sig = self.bind_name(f'prod_{i}_{j}', value_hist[j]*(step[j][i]-step[j-1][i])) prod.append(prod_sig) # define model behavior self.set_this_cycle(outputs[i], sum_op(prod))
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]
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()) m.add_analog_input('in_') m.add_analog_output('out') m.add_analog_input('dt_sig') m.add_digital_input('clk') m.add_digital_input('cke') m.add_digital_input('rst') # 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) # create a history of past inputs cke_d = m.add_digital_state('cke_d') m.set_next_cycle(cke_d, m.cke, clk=m.clk, rst=m.rst) value_hist = m.make_history(m.in_, system_values['num_terms'] + 1, clk=m.clk, rst=m.rst, ce=cke_d) # create a history times in the past when the input changed time_incr = [] time_mux = [] for k in range(system_values['num_terms'] + 1): if k == 0: time_incr.append(m.dt_sig) time_mux.append(None) else: # create the signal mem_sig = AnalogState(name=f'time_mem_{k}', range_=m.dt_sig.format_.range_, width=m.dt_sig.format_.width, exponent=m.dt_sig.format_.exponent, init=0.0) m.add_signal(mem_sig) # increment time by dt_sig (this is the output from the current tap) incr_sig = m.bind_name(f'time_incr_{k}', mem_sig + m.dt_sig) time_incr.append(incr_sig) # mux input of DFF between current and previous memory value mux_sig = m.bind_name( f'time_mux_{k}', if_(m.cke_d, time_incr[k - 1], time_incr[k])) time_mux.append(mux_sig) # delayed assignment m.set_next_cycle(signal=mem_sig, expr=mux_sig, clk=m.clk, rst=m.rst) # evaluate step response function step = [] for k in range(system_values['num_terms']): step_sig = m.set_from_sync_func(f'step_{k}', chan_func, time_mux[k + 1], clk=m.clk, rst=m.rst, wdata=wdata, waddr=waddr, we=we) step.append(step_sig) # compute the products to be summed prod = [] for k in range(system_values['num_terms']): if k == 0: prod_sig = m.bind_name(f'prod_{k}', value_hist[k + 1] * step[k]) else: prod_sig = m.bind_name( f'prod_{k}', value_hist[k + 1] * (step[k] - step[k - 1])) prod.append(prod_sig) # define model behavior m.set_this_cycle(m.out, sum_op(prod)) # 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(1) 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' # instantiate model m = MixedSignalModel(module_name, dt=system_values['dt'], build_dir=build_dir) # main I/O: delay code and gain m.add_digital_input('code', width=system_values['n_bits']) m.add_digital_input('clk_i_val') m.add_digital_output('clk_o_val') # timestep control: DT request and response m.add_analog_output('dt_req') m.add_analog_input('emu_dt') # emulator clock and reset m.add_digital_input('emu_clk') m.add_digital_input('emu_rst') # additional input: maximum timestep # TODO: clean this up m.add_analog_input('dt_req_max') # jitter control m.add_digital_input('jitter_seed', width=32) m.add_analog_input('jitter_rms') # compute the delay (with no jitter) m.bind_name('delay_amt_pre', m.code * (system_values['t_per'] / (2.0 ** system_values['n_bits']))) # add jitter to the delay amount (which might possibly yield a negative value) m.set_gaussian_noise('t_jitter', std=m.jitter_rms, clk=m.emu_clk, rst=m.emu_rst, lfsr_init=m.jitter_seed) m.bind_name('delay_amt_noisy', m.delay_amt_pre + m.t_jitter) # make the delay amount non-negative m.bind_name('delay_amt', if_(m.delay_amt_noisy >= 0.0, m.delay_amt_noisy, 0.0)) # determine when the clock value has changed m.add_digital_state('clk_i_val_prev') m.set_next_cycle(m.clk_i_val_prev, m.clk_i_val, clk=m.emu_clk, rst=m.emu_rst) m.bind_name('clk_edge', m.clk_i_val ^ m.clk_i_val_prev) # create pointer that advances each time there is a clock edge depth = system_values['depth'] dbits = int(ceil(log2(depth))) m.add_digital_state('addr', width=dbits) m.add_digital_state('next_addr', width=dbits) m.set_this_cycle(m.next_addr, if_(m.addr == (depth-1), 0, m.addr+1)) m.set_next_cycle(m.addr, m.next_addr, clk=m.emu_clk, rst=m.emu_rst, ce=m.clk_edge) # convenience function for formatting DT signals def add_dt_state(*args, range_=m.emu_dt.format_.range_, width=m.emu_dt.format_.width, exponent=m.emu_dt.format_.exponent, **kwargs): return m.add_analog_state(*args, range_=range_, width=width, exponent=exponent, **kwargs) # convenience function for formatting DT signals def dt_array(*args, real_range_hint=m.emu_dt.format_.range_, width=m.emu_dt.format_.width, exponent=m.emu_dt.format_.exponent, **kwargs): return array(*args, real_range_hint=real_range_hint, width=width, exponent=exponent, **kwargs) # instantiate delay "units" that each keep track of one edge dt_req = [] req_data = [] req_grant = [] req_valid = [] for k in range(depth): # should load data if there is a clock edge and this slice is selected load_data = m.bind_name(f'load_data_{k}', m.clk_edge & (k == m.addr)) # handle update of dt_req dt_req.append(add_dt_state(f'dt_req_{k}')) m.set_next_cycle( dt_req[-1], if_(load_data, m.delay_amt, dt_req[-1] - m.emu_dt), clk=m.emu_clk, rst=m.emu_rst ) # handle update of req_data req_data.append(m.add_digital_state(f'req_data_{k}')) m.set_next_cycle(req_data[-1], m.clk_i_val, ce=load_data, clk=m.emu_clk, rst=m.emu_rst) # handle update of req_grant req_grant.append(m.bind_name(f'req_grant_{k}', dt_req[-1] == m.emu_dt)) # handle update of req_valid req_valid.append(m.add_digital_state(f'req_valid_{k}')) m.set_next_cycle(req_valid[-1], (req_valid[-1] & (~req_grant[-1])) | load_data, clk=m.emu_clk, rst=m.emu_rst) # replace dt_req with dt_req_max for invalid requests dt_req_mux = [] for k in range(depth): dt_req_mux.append( m.bind_name( f'dt_req_mux_{k}', dt_array( [m.dt_req_max, dt_req[k]], req_valid[k], ) ) ) # convenience function to find the minimum DT request using a tree structure counter = count() def tree_min(data): # check cases if len(data) == 0: raise Exception("This shouldn't happen...") elif len(data) == 1: return data[0] else: val0 = tree_min(data[:len(data)//2]) val1 = tree_min(data[len(data)//2:]) return m.bind_name( f'dt_intern_{next(counter)}', dt_array( [val0, val1], val1 < val0, ) ) # set the "dt_req" output to the minimum time request (where invalid requests # are replaced with "dt_req_max" m.set_this_cycle(m.dt_req, tree_min(dt_req_mux)) # determine if the output should be set or cleared set_out = req_grant[0] & req_valid[0] & req_data[0] clr_out = req_grant[0] & req_valid[0] & (~req_data[0]) for k in range(1, depth): set_out = set_out | (req_grant[k] & req_valid[k] & req_data[k]) clr_out = clr_out | (req_grant[k] & req_valid[k] & (~req_data[k])) m.bind_name('set_out', set_out) m.bind_name('clr_out', clr_out) # set output, clear output, or keep it the same m.add_digital_state('clk_o_val_prev') m.set_next_cycle(m.clk_o_val_prev, m.clk_o_val, clk=m.emu_clk, rst=m.emu_rst) m.set_this_cycle(m.clk_o_val, (m.clk_o_val_prev & (~m.clr_out)) | (m.set_out)) # generate the model m.compile_to_file(VerilogGenerator()) self.generated_files = [filename]