def test_sim(simulator_name, n_prbs, n_channels=16, n_trials=256): # set defaults if simulator_name is None: if shutil.which('iverilog'): simulator_name = 'iverilog' else: simulator_name = 'ncsim' # determine the right equation if n_prbs == 7: eqn = (1 << 6) | (1 << 5) elif n_prbs == 9: eqn = (1 << 8) | (1 << 4) elif n_prbs == 11: eqn = (1 << 10) | (1 << 8) elif n_prbs == 15: eqn = (1 << 14) | (1 << 13) elif n_prbs == 17: eqn = (1 << 16) | (1 << 13) elif n_prbs == 20: eqn = (1 << 19) | (1 << 2) elif n_prbs == 23: eqn = (1 << 22) | (1 << 17) elif n_prbs == 29: eqn = (1 << 28) | (1 << 26) elif n_prbs == 31: eqn = (1 << 30) | (1 << 27) else: raise Exception(f'Unknown value for n_prbs: {n_prbs}') # declare circuit class dut(m.Circuit): name = 'test_prbs_checker' io = m.IO( rst=m.BitIn, eqn=m.In(m.Bits[n_prbs]), checker_mode=m.In(m.Bits[2]), delay=m.In(m.Bits[5]), clk_div=m.BitOut, err_bits=m.Out(m.Bits[64]), total_bits=m.Out(m.Bits[64]), clk_bogus=m. ClockIn # need to have clock signal in order to wait on posedges (possible fault bug?) ) # create tester t = fault.Tester(dut, dut.clk_bogus) # initialize with the right equation t.poke(dut.rst, 1) t.poke(dut.eqn, eqn) t.poke(dut.checker_mode, RESET) t.poke(dut.delay, randint(0, 31)) for _ in range(10): t.wait_until_posedge(dut.clk_div) # release from reset and run for a bit t.poke(dut.rst, 0) for _ in range(2 * n_prbs): t.wait_until_posedge(dut.clk_div) # check for errors t.poke(dut.checker_mode, TEST) for _ in range(n_trials): t.wait_until_posedge(dut.clk_div) # freeze, wait, then read out values t.poke(dut.checker_mode, FREEZE) for _ in range(10): t.wait_until_posedge(dut.clk_div) err_bits_case_1 = t.get_value(dut.err_bits) total_bits_case_1 = t.get_value(dut.total_bits) # initialize with the wrong equation t.poke(dut.checker_mode, RESET) t.poke(dut.eqn, eqn + 1) for _ in range(2 * n_prbs): t.wait_until_posedge(dut.clk_div) # check for errors t.poke(dut.checker_mode, TEST) for _ in range(n_trials): t.wait_until_posedge(dut.clk_div) # freeze, wait, then read out values t.poke(dut.checker_mode, FREEZE) for _ in range(10): t.wait_until_posedge(dut.clk_div) err_bits_case_2 = t.get_value(dut.err_bits) total_bits_case_2 = t.get_value(dut.total_bits) # run the test t.compile_and_run(target='system-verilog', simulator=simulator_name, ext_srcs=[ get_file('vlog/tb/prbs_generator.sv'), get_file('vlog/chip_src/prbs/prbs_checker_core.sv'), get_file('vlog/chip_src/prbs/prbs_checker.sv'), THIS_DIR / 'test_prbs_checker.sv' ], parameters={ 'n_prbs': n_prbs, 'n_channels': n_channels }, ext_model_file=True, disp_type='realtime', dump_waveforms=False, directory=BUILD_DIR, num_cycles=1e12) print(f'err_bits_case_1: {err_bits_case_1.value}') print(f'total_bits_case_1: {total_bits_case_1.value}') print(f'err_bits_case_2: {err_bits_case_2.value}') print(f'total_bits_case_2: {total_bits_case_2.value}') assert err_bits_case_1.value == 0, \ 'Should be no errors (case 1)' assert total_bits_case_1.value == n_channels*n_trials, \ 'Wrong number of total bits (case 1)' assert err_bits_case_2.value > 0, \ 'Some errors should be detected when the wrong equation is used' assert total_bits_case_2.value == n_channels*n_trials, \ 'Wrong number of total bits (case 2)' print('Success!')
def test_clk_delay(simulator_name, float_real): # set defaults if simulator_name is None: simulator_name = 'vivado' # declare circuit class dut(m.Circuit): name = 'test_clk_delay' io = m.IO(code=m.In(m.Bits[N_BITS]), clk_i_val=m.BitIn, clk_o_val=m.BitOut, dt_req=fault.RealIn, emu_dt=fault.RealOut, jitter_seed=m.In(m.Bits[32]), jitter_rms=fault.RealIn, emu_clk=m.In(m.Clock), emu_rst=m.BitIn) # create the tester t = fault.Tester(dut, dut.emu_clk) # utility function for easier waveform debug def check_result(emu_dt, clk_val, abs_tol=DEL_PREC): t.delay((TCLK / 2) - DELTA) t.poke(dut.emu_clk, 0) t.delay(TCLK / 2) t.expect(dut.emu_dt, emu_dt, abs_tol=abs_tol) t.expect(dut.clk_o_val, clk_val) t.poke(dut.emu_clk, 1) t.delay(DELTA) # compute nominal delay del_nom = (DEL_CODE / (2.0**N_BITS)) * T_PER # initialize t.zero_inputs() t.poke(dut.code, DEL_CODE) t.poke(dut.emu_rst, 1) # apply reset t.poke(dut.emu_clk, 1) t.delay(TCLK / 2) t.poke(dut.emu_clk, 0) t.delay(TCLK / 2) # clear reset t.poke(dut.emu_rst, 0) # run a few cycles for _ in range(3): t.poke(dut.emu_clk, 1) t.delay(TCLK / 2) t.poke(dut.emu_clk, 0) t.delay(TCLK / 2) t.poke(dut.emu_clk, 1) t.delay(DELTA) # raise clock val t.poke(dut.clk_i_val, 1) t.poke(dut.dt_req, (0.123e-9) / 4) check_result((0.123e-9) / 4, 0) # wait some time (expect dt_req ~ 0.1 ns) t.poke(dut.clk_i_val, 1) t.poke(dut.dt_req, 0.234e-9) check_result(del_nom, 1) # lower clk_val, wait some time (expect dt_req ~ 0.345 ns) t.poke(dut.clk_i_val, 0) t.poke(dut.dt_req, (0.345e-9) / 4) check_result((0.345e-9) / 4, 1) # wait some time (expect dt_req ~ 0.1 ns) t.poke(dut.clk_i_val, 0) t.poke(dut.dt_req, (0.456e-9) / 4) check_result(del_nom, 0) # raise clk_val, wait some time (expect dt_req ~ 0.567 ns) t.poke(dut.clk_i_val, 1) t.poke(dut.dt_req, (0.567e-9) / 4) check_result((0.567e-9) / 4, 0) # wait some time (expect dt_req ~ 0.1 ns) t.poke(dut.clk_i_val, 1) t.poke(dut.dt_req, (0.678e-9) / 4) check_result(del_nom, 1) # lower clk_val, wait some time (expect dt_req ~ 0.789 ns) t.poke(dut.clk_i_val, 0) t.poke(dut.dt_req, (0.789e-9) / 4) check_result((0.789e-9) / 4, 1) # run the simulation defines = {'DT_WIDTH': 25, 'DT_EXPONENT': -46} if float_real: defines['FLOAT_REAL'] = None t.compile_and_run( target='system-verilog', directory=BUILD_DIR, simulator=simulator_name, ext_srcs=[ get_file('build/fpga_models/clk_delay_core/clk_delay_core.sv'), get_file('tests/fpga_block_tests/clk_delay/test_clk_delay.sv') ], inc_dirs=[get_svreal_header().parent, get_msdsl_header().parent], ext_model_file=True, defines=defines, disp_type='realtime')
# FPGA-specific imports from svreal import get_svreal_header from msdsl import get_msdsl_header from msdsl.function import PlaceholderFunction # DragonPHY imports from dragonphy import get_file, Filter BUILD_DIR = Path(__file__).resolve().parent / 'build' DELTA = 100e-9 TPER = 1e-6 # read YAML file that was used to configure the generated model CFG = yaml.load(open(get_file('config/fpga/chan.yml'), 'r')) # read channel data CHAN = Filter.from_file(get_file('build/chip_src/adapt_fir/chan.npy')) def test_chan_model(simulator_name): # set defaults if simulator_name is None: simulator_name = 'vivado' # declare circuit class dut(m.Circuit): name = 'test_chan_model' io = m.IO(in_=fault.RealIn, out=fault.RealOut,
def test_chan_model(simulator_name): # set defaults if simulator_name is None: simulator_name = 'vivado' # declare circuit class dut(m.Circuit): name = 'test_chan_model' io = m.IO(in_=fault.RealIn, out=fault.RealOut, dt_sig=fault.RealIn, clk=m.In(m.Clock), cke=m.BitIn, rst=m.BitIn, wdata0=m.In(m.Bits[CFG['func_widths'][0]]), wdata1=m.In(m.Bits[CFG['func_widths'][1]]), waddr=m.In(m.Bits[int(ceil(log2(CFG['func_numel'])))]), we=m.BitIn) # create the t t = fault.Tester(dut, dut.clk) def cycle(k=1): for _ in range(k): t.delay(TPER / 2 - DELTA) t.poke(dut.clk, 0) t.delay(TPER / 2) t.poke(dut.clk, 1) t.delay(DELTA) # initialize t.zero_inputs() t.poke(dut.rst, 1) cycle() t.poke(dut.rst, 0) # initialize step response functions placeholder = PlaceholderFunction(domain=CFG['func_domain'], order=CFG['func_order'], numel=CFG['func_numel'], coeff_widths=CFG['func_widths'], coeff_exps=CFG['func_exps']) coeffs_bin = placeholder.get_coeffs_bin_fmt(CHAN.interp) t.poke(dut.we, 1) for i in range(placeholder.numel): t.poke(dut.wdata0, coeffs_bin[0][i]) t.poke(dut.wdata1, coeffs_bin[1][i]) t.poke(dut.waddr, i) cycle() t.poke(dut.we, 0) # values val1 = +1.23 val2 = -2.34 val3 = +3.45 # timesteps dt0 = (1e-9) / 16 dt1 = (2e-9) / 16 dt2 = (3e-9) / 16 dt3 = (4e-9) / 16 dt4 = (5e-9) / 16 dt5 = (6e-9) / 16 dt6 = (7e-9) / 16 dt7 = (8e-9) / 16 dt8 = (9e-9) / 16 # action sequence t.poke(dut.cke, 0) t.poke(dut.dt_sig, dt0) t.poke(dut.in_, 0.0) cycle() t.poke(dut.cke, 1) t.poke(dut.dt_sig, dt1) t.poke(dut.in_, 0.0) cycle() meas1 = t.get_value(dut.out) t.poke(dut.cke, 0) t.poke(dut.dt_sig, dt2) t.poke(dut.in_, val1) cycle() meas2 = t.get_value(dut.out) t.poke(dut.cke, 0) t.poke(dut.dt_sig, dt3) t.poke(dut.in_, val1) cycle() meas3 = t.get_value(dut.out) t.poke(dut.cke, 1) t.poke(dut.dt_sig, dt4) t.poke(dut.in_, val1) cycle() meas4 = t.get_value(dut.out) t.poke(dut.cke, 0) t.poke(dut.dt_sig, dt5) t.poke(dut.in_, val2) cycle() meas5 = t.get_value(dut.out) t.poke(dut.cke, 1) t.poke(dut.dt_sig, dt6) t.poke(dut.in_, val2) cycle() meas6 = t.get_value(dut.out) t.poke(dut.cke, 0) t.poke(dut.dt_sig, dt7) t.poke(dut.in_, val3) cycle() meas7 = t.get_value(dut.out) t.poke(dut.cke, 0) t.poke(dut.dt_sig, dt8) t.poke(dut.in_, val3) cycle() meas8 = t.get_value(dut.out) # compute expected outputs chan = Filter.from_file(get_file('build/chip_src/adapt_fir/chan.npy')) f = chan.interp expt1 = 0 expt2 = val1 * f(dt2) expt3 = val1 * f(dt2 + dt3) expt4 = val1 * f(dt2 + dt3 + dt4) expt5 = val1 * (f(dt2 + dt3 + dt4 + dt5) - f(dt5)) + val2 * f(dt5) expt6 = val1 * (f(dt2 + dt3 + dt4 + dt5 + dt6) - f(dt5 + dt6)) + val2 * f(dt5 + dt6) expt7 = (val1 * (f(dt2 + dt3 + dt4 + dt5 + dt6 + dt7) - f(dt5 + dt6 + dt7)) + val2 * (f(dt5 + dt6 + dt7) - f(dt7)) + val3 * f(dt7)) # define parameters parameters = { 'width0': CFG['func_widths'][0], 'width1': CFG['func_widths'][1], 'naddr': int(ceil(log2(CFG['func_numel']))) } # run the simulation t.compile_and_run( target='system-verilog', directory=BUILD_DIR, simulator=simulator_name, ext_srcs=[ get_file('build/fpga_models/chan_core/chan_core.sv'), get_file('tests/fpga_block_tests/chan_model/test_chan_model.sv') ], inc_dirs=[get_svreal_header().parent, get_msdsl_header().parent], ext_model_file=True, disp_type='realtime', parameters=parameters, dump_waveforms=False, timescale='1ns/1ps', num_cycles=1e12) # check outputs def check_output(name, meas, expct, abs_tol=0.001): print(f'checking {name}: measured {meas.value}, expected {expct}') if (expct - abs_tol) <= meas.value <= (expct + abs_tol): print('OK') else: raise Exception('Failed') check_output('expr1', meas1, expt1) check_output('expr2', meas2, expt2) check_output('expr3', meas3, expt3) check_output('expr4', meas4, expt4) check_output('expr5', meas5, expt5) check_output('expr6', meas6, expt6) check_output('expr7', meas7, expt7)
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 test_analog_slice(simulator_name, slice_offset, dump_waveforms, num_tests=100): # set seed for repeatable behavior random.seed(0) # set defaults if simulator_name is None: simulator_name = 'vivado' # determine the real-number formatting real_type = get_dragonphy_real_type() if real_type in {RealType.FixedPoint, RealType.FloatReal}: func_widths = CFG['func_widths'] elif real_type == RealType.HardFloat: func_widths = [DEF_HARD_FLOAT_WIDTH, DEF_HARD_FLOAT_WIDTH] else: raise Exception('Unsupported RealType.') # declare circuit class dut(m.Circuit): name = 'test_analog_slice' io = m.IO( chunk=m.In(m.Bits[CFG['chunk_width']]), chunk_idx=m.In(m.Bits[int(ceil(log2(CFG['num_chunks'])))]), pi_ctl=m.In(m.Bits[CFG['pi_ctl_width']]), slice_offset=m.In(m.Bits[int(ceil(log2(CFG['slices_per_bank'])))]), sample_ctl=m.BitIn, incr_sum=m.BitIn, write_output=m.BitIn, out_sgn=m.BitOut, out_mag=m.Out(m.Bits[CFG['n_adc']]), clk=m.BitIn, rst=m.BitIn, jitter_rms=fault.RealIn, noise_rms=fault.RealIn, wdata0=m.In(m.Bits[func_widths[0]]), wdata1=m.In(m.Bits[func_widths[1]]), waddr=m.In(m.Bits[9]), we=m.BitIn ) # create the tester t = fault.Tester(dut) def cycle(k=1): for _ in range(k): t.delay(TPER/2 - DELTA) t.poke(dut.clk, 0) t.delay(TPER/2) t.poke(dut.clk, 1) t.delay(DELTA) def to_bv(lis): return int(''.join(str(elem) for elem in lis), 2) # build up a list of test cases test_cases = [] for x in range(num_tests): pi_ctl = random.randint(0, (1 << CFG['pi_ctl_width']) - 1) all_bits = [random.randint(0, 1) for _ in range(CFG['chunk_width'] * CFG['num_chunks'])] test_cases.append([pi_ctl, all_bits]) # initialize # two cycles of reset -- one to reset DFFs, then another to read out ROM values # after the addresses are no longer X t.zero_inputs() t.poke(dut.slice_offset, slice_offset) t.poke(dut.rst, 1) cycle(2) t.poke(dut.rst, 0) # initialize step response functions placeholder = PlaceholderFunction(domain=CFG['func_domain'], order=CFG['func_order'], numel=CFG['func_numel'], coeff_widths=CFG['func_widths'], coeff_exps=CFG['func_exps'], real_type=real_type) coeffs_bin = placeholder.get_coeffs_bin_fmt(CHAN.interp) t.poke(dut.we, 1) for i in range(placeholder.numel): t.poke(dut.wdata0, coeffs_bin[0][i]) t.poke(dut.wdata1, coeffs_bin[1][i]) t.poke(dut.waddr, i) cycle() t.poke(dut.we, 0) # f cycle t.poke(dut.chunk, 0) t.poke(dut.chunk_idx, 0) t.poke(dut.pi_ctl, test_cases[0][0]) t.poke(dut.sample_ctl, 1) t.poke(dut.incr_sum, 1) t.poke(dut.write_output, 1) cycle() # loop through test cases for i in range(num_tests): for j in range(2+CFG['num_chunks']): if j < CFG['num_chunks']: t.poke(dut.chunk, to_bv(test_cases[i][1][(j*CFG['chunk_width']):((j+1)*CFG['chunk_width'])])) t.poke(dut.chunk_idx, j) else: t.poke(dut.chunk, 0) t.poke(dut.chunk_idx, 0) if j != (CFG['num_chunks']+1): t.poke(dut.sample_ctl, 0) t.poke(dut.write_output, 0) else: if i != (num_tests-1): t.poke(dut.pi_ctl, test_cases[i+1][0]) t.poke(dut.sample_ctl, 1) t.poke(dut.write_output, 1) if j != 1: t.poke(dut.incr_sum, 1) else: t.poke(dut.incr_sum, 0) cycle() # gather results test_cases[i].extend([t.get_value(dut.out_sgn), t.get_value(dut.out_mag)]) # define macros defines = {} # include directories inc_dirs = [ get_svreal_header().parent, get_msdsl_header().parent ] # source files ext_srcs = [ get_file('build/fpga_models/analog_slice/analog_slice.sv'), THIS_DIR / 'test_analog_slice.sv' ] # adjust for HardFloat if needed if real_type == RealType.HardFloat: ext_srcs = get_hard_float_sources() + ext_srcs inc_dirs = get_hard_float_inc_dirs() + inc_dirs defines['HARD_FLOAT'] = None defines['FUNC_DATA_WIDTH'] = DEF_HARD_FLOAT_WIDTH # waveform dumping options flags = [] if simulator_name == 'ncsim': flags += ['-unbuffered'] # run the simulation t.compile_and_run( target='system-verilog', directory=BUILD_DIR, simulator=simulator_name, defines=defines, inc_dirs=inc_dirs, ext_srcs=ext_srcs, parameters={ 'chunk_width': CFG['chunk_width'], 'num_chunks': CFG['num_chunks'] }, ext_model_file=True, disp_type='realtime', dump_waveforms=dump_waveforms, flags=flags, timescale='1fs/1fs', num_cycles=1e12 ) # process the results for k, (pi_ctl, all_bits, sgn_meas, mag_meas) in enumerate(test_cases): # compute the expected ADC value analog_sample = channel_model(slice_offset, pi_ctl, all_bits) sgn_expct, mag_expct = adc_model(analog_sample) # print measured and expected print(f'Test case #{k}: slice_offset={slice_offset}, pi_ctl={pi_ctl}, all_bits={all_bits}') print(f'[measured] sgn: {sgn_meas.value}, mag: {mag_meas.value}') print(f'[expected] sgn: {sgn_expct}, mag: {mag_expct}, analog_sample: {analog_sample}') # check result check_adc_result(sgn_meas.value, mag_meas.value, sgn_expct, mag_expct) # declare success print('Success!')
def test_sim(simulator_name, n_prbs, n_channels=16, n_trials=256): # set defaults if simulator_name is None: if shutil.which('iverilog'): simulator_name = 'iverilog' else: simulator_name = 'ncsim' # determine the right equation if n_prbs == 7: eqn = (1 << 6) | (1 << 5) elif n_prbs == 9: eqn = (1 << 8) | (1 << 4) elif n_prbs == 11: eqn = (1 << 10) | (1 << 8) elif n_prbs == 15: eqn = (1 << 14) | (1 << 13) elif n_prbs == 17: eqn = (1 << 16) | (1 << 13) elif n_prbs == 20: eqn = (1 << 19) | (1 << 2) elif n_prbs == 23: eqn = (1 << 22) | (1 << 17) elif n_prbs == 29: eqn = (1 << 28) | (1 << 26) elif n_prbs == 31: eqn = (1 << 30) | (1 << 27) else: raise Exception(f'Unknown value for n_prbs: {n_prbs}') # declare circuit class dut(m.Circuit): name = 'test_prbs_checker_core' io = m.IO( rst=m.BitIn, eqn=m.In(m.Bits[n_prbs]), delay=m.In(m.Bits[5]), clk_div=m.BitOut, err=m.BitOut, clk_bogus=m.ClockIn # need to have clock signal in order to wait on posedges (possible fault bug?) ) # create tester t = fault.Tester(dut, dut.clk_bogus) # initialize with the right equation t.poke(dut.rst, 1) t.poke(dut.eqn, eqn) t.poke(dut.delay, randint(0, 31)) for _ in range(10): t.wait_until_posedge(dut.clk_div) # release from reset and run for a bit t.poke(dut.rst, 0) for _ in range(2*n_prbs): t.wait_until_posedge(dut.clk_div) # check for errors for _ in range(n_trials): t.wait_until_posedge(dut.clk_div) t.expect(dut.err, 0) # initialize with the wrong equation t.poke(dut.rst, 1) t.poke(dut.eqn, eqn+1) for _ in range(10): t.wait_until_posedge(dut.clk_div) # release from reset and run for a bit t.poke(dut.rst, 0) for _ in range(2*n_prbs): t.wait_until_posedge(dut.clk_div) # check for errors vals_with_wrong_equation = [] for _ in range(n_trials): t.wait_until_posedge(dut.clk_div) vals_with_wrong_equation.append(t.get_value(dut.err)) # run the test t.compile_and_run( target='system-verilog', simulator=simulator_name, ext_srcs=[ get_file('vlog/tb/prbs_generator.sv'), get_file('vlog/chip_src/prbs/prbs_checker_core.sv'), THIS_DIR / 'test_prbs_checker_core.sv' ], parameters={ 'n_prbs': n_prbs, 'n_channels': n_channels }, ext_model_file=True, disp_type='realtime', dump_waveforms=False, directory=BUILD_DIR, num_cycles=1e12 ) # post-process results errors_with_wrong_equation = 0 for elem in vals_with_wrong_equation: errors_with_wrong_equation += elem.value assert errors_with_wrong_equation > 0, \ 'Should have detected errors when using the wrong equation.' print('Success!')
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 test_adc_model(simulator_name, should_print=False): # set defaults if simulator_name is None: simulator_name = 'vivado' # declare circuit class dut(m.Circuit): name = 'test_adc_model' io = m.IO( in_=fault.RealIn, clk_val=m.BitIn, out_mag=m.Out(m.Bits[CFG['n']]), out_sgn=m.BitOut, noise_seed=m.In(m.Bits[32]), noise_rms=fault.RealIn, emu_rst=m.BitIn, emu_clk=m.ClockIn ) # create the tester t = fault.Tester(dut, dut.emu_clk) # initialize t.zero_inputs() t.poke(dut.emu_rst, 1) t.step(4) # clear reset t.poke(dut.emu_rst, 0) t.step(4) # create mechanism to run trials def run_trial(in_): # set input t.poke(dut.in_, in_) t.step(4) # toggle clock t.poke(dut.clk_val, 1) t.step(4) t.poke(dut.clk_val, 0) t.step(4) # print output if desired if should_print: t.print('in_: %0f, out_sgn: %0d, out_mag: %0d\n', dut.in_, dut.out_sgn, dut.out_mag) # get expected output expct_sgn, expct_mag = model(in_=in_) # check results t.expect(dut.out_sgn, expct_sgn) t.expect(dut.out_mag, expct_mag) # specify trials to be run delta = 0.1*CFG['vref'] for in_ in np.linspace(-CFG['vref']-delta, CFG['vref']+delta, 100): run_trial(in_=in_) # run the simulation t.compile_and_run( target='system-verilog', directory=BUILD_DIR, simulator=simulator_name, ext_srcs=[get_file('build/fpga_models/rx_adc_core/rx_adc_core.sv'), get_file('tests/fpga_block_tests/adc_model/test_adc_model.sv')], inc_dirs=[get_svreal_header().parent, get_msdsl_header().parent], ext_model_file=True, disp_type='realtime', parameters={'n': CFG['n']}, dump_waveforms=False )
# AHA imports import magma as m import fault # FPGA-specific imports from svreal import get_svreal_header from msdsl import get_msdsl_header # DragonPHY imports from dragonphy import get_file BUILD_DIR = Path(__file__).resolve().parent / 'build' # read YAML file that was used to configure the generated model CFG = yaml.load(open(get_file('config/fpga/rx_adc.yml'), 'r')) def model(in_): # determine sign if in_ < 0: sgn = 0 else: sgn = 1 # determine magnitude in_abs = abs(in_) mag_real = (abs(in_abs) / CFG['vref']) * ((2**(CFG['n']-1)) - 1) mag_unclamped = int(floor(mag_real)) mag = min(max(mag_unclamped, 0), (2**(CFG['n']-1))-1) # return result
def test_sim(simulator_name, dump_waveforms, prbs_eqn=0b1100000, n_prbs=32): # set defaults if simulator_name is None: if shutil.which('iverilog'): simulator_name = 'iverilog' else: simulator_name = 'ncsim' # declare circuit class dut(m.Circuit): name = 'prbs_generator_syn' io = m.IO( clk=m.ClockIn, rst=m.BitIn, cke=m.BitIn, init_val=m.In(m.Bits[n_prbs]), eqn=m.In(m.Bits[n_prbs]), inj_err=m.BitIn, inv_chicken=m.In(m.Bits[2]), out=m.BitOut ) # create tester t = fault.Tester(dut, dut.clk) # initialize with the right equation t.zero_inputs() t.poke(dut.rst, 1) t.poke(dut.cke, 1) t.poke(dut.init_val, 1) t.poke(dut.eqn, prbs_eqn) # reset for _ in range(3): t.step(2) # test 1 t.poke(dut.rst, 0) prbs_vals_1 = [] for k in range(200): t.step(2) prbs_vals_1.append(t.get_value(dut.out)) # test 2 prbs_vals_2 = [] for k in range(100): t.poke(dut.inj_err, 1 if k==50 else 0) t.step(2) prbs_vals_2.append(t.get_value(dut.out)) # test 3 prbs_vals_3 = [] for k in range(100): t.step(2) prbs_vals_3.append(t.get_value(dut.out)) # run the test t.compile_and_run( target='system-verilog', simulator=simulator_name, ext_srcs=[ get_file('vlog/chip_src/prbs/prbs_generator_syn.sv'), ], ext_model_file=True, disp_type='realtime', dump_waveforms=dump_waveforms, directory=BUILD_DIR, num_cycles=1e12 ) # get results prbs_vals_1 = [x.value for x in prbs_vals_1] prbs_vals_2 = [x.value for x in prbs_vals_2] prbs_vals_3 = [x.value for x in prbs_vals_3] # check test 1 prbs_vals_1 = prbs_vals_1[50:] verify_prbs(prbs_vals=prbs_vals_1, prbs_eqn=prbs_eqn, n_ti=1, n_prbs=n_prbs) # check test 2 err_count = count_prbs_err(prbs_vals=prbs_vals_2, prbs_eqn=prbs_eqn, n_ti=1, n_prbs=n_prbs) assert err_count == 3 # check test 3 verify_prbs(prbs_vals=prbs_vals_3, prbs_eqn=prbs_eqn, n_ti=1, n_prbs=n_prbs) # declare success print('Success!')