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)
Example #5
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]
Example #6
0
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!')
Example #7
0
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!')
Example #8
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())
        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]
Example #9
0
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
    )
Example #10
0
# 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
Example #11
0
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!')