def connect_vio(self):
        # Create analysis object
        self.ana = Analysis(input=get_dir('tests/fpga_system_tests/emu_macro'))

        # Select the "FPGA" target (as opposed to CPU simulation)
        self.ana.set_target(target_name='fpga')

        # Start the interactive control
        self.ctrl = self.ana.launch(debug=False)
Exemple #2
0
def test_3():
    ana = Analysis(input=root)
    ana.gen_sources()
    ana.set_target(target_name='fpga')
    ctrl = ana.launch(debug=True)

    # reset sequence
    # the RNG takes 25000 cycles to start up, but we wait
    # 10x longer to be extra sure (i.e., 25ms)
    ctrl.set_param('cdf_rst', 1)
    ctrl.set_reset(1)
    ctrl.set_reset(0)
    time.sleep(25e-3)
    ctrl.set_param('cdf_rst', 0)

    # run for some time (each second yields 10M points)
    time.sleep(100)

    # read out results
    ctrl.stall_emu()
    ctrl.refresh_param('vio_0_i')
    cdfs = []
    cdf_tot = int(ctrl.get_param('cdf_tot'))
    for i in range(MIN_CDF, MAX_CDF + 1):
        cdfs.append(int(ctrl.get_param(f'cdf_{numstr(i)}')))

    # print results
    print(f'cdf_tot: {cdf_tot}')
    for i in range(MIN_CDF, MAX_CDF + 1):
        print(f'{i}: {cdfs[i-MIN_CDF]/cdf_tot}')
Exemple #3
0
def test_2(simulator_name):
    # set defaults
    if simulator_name is None:
        simulator_name = 'vivado'

    # run simulation
    ana = Analysis(input=str(THIS_DIR), simulator_name=simulator_name)
    ana.set_target(target_name='sim')
    ana.simulate(convert_waveform=False)
Exemple #4
0
def setup_target(test_name, test_root, target, simulator=None, synthesizer=None, viewer=None):
    ana = Analysis(input=os.path.join(test_root, test_name),
                   build_root=os.path.join(BuildRoot.get_build_root, 'test_' + test_name),
                   simulator_name=simulator,
                   synthesizer_name=synthesizer,
                   viewer_name=viewer)
    ana.gen_sources()
    ana.set_target(target_name=target)
    return ana
Exemple #5
0
def test_6(prbs_test_dur, jitter_rms, noise_rms, chan_tau, chan_delay):
    # read ffe_length
    SYSTEM = yaml.load(open(get_file('config/system.yml'), 'r'),
                       Loader=yaml.FullLoader)
    ffe_length = SYSTEM['generic']['ffe']['parameters']['length']

    jtag_inst_width = 5
    sc_bus_width = 32
    sc_addr_width = 14

    tc_bus_width = 32
    tc_addr_width = 14

    sc_op_width = 2
    tc_op_width = 2

    sc_cfg_data = 8
    sc_cfg_inst = 9
    sc_cfg_addr = 10
    tc_cfg_data = 12
    tc_cfg_inst = 13
    tc_cfg_addr = 14

    read = 1
    write = 2

    reg_list = json.load(open(get_file('build/all/jtag/reg_list.json'), 'r'))
    reg_dict = {elem['name']: elem['addresses'] for elem in reg_list}
    arr_pat = re.compile(r'([a-zA-Z_0-9]+)+\[(\d+)\]')

    def get_reg_addr(name):
        m = arr_pat.match(name)
        if m:
            name = m.groups()[0]
            index = int(m.groups()[1])
            return reg_dict[name][index]
        else:
            return reg_dict[name]

    # connect to the CPU
    print('Connecting to the CPU...')
    ana = Analysis(input=str(THIS_DIR))
    ana.set_target(target_name='fpga')
    ctrl = ana.launch()
    ser = ctrl.ctrl_handler

    # functions
    def do_reset():
        ser.write('RESET\n'.encode('utf-8'))

    def do_init():
        ser.write('INIT\n'.encode('utf-8'))

    def set_emu_rst(val):
        ser.write(f'SET_EMU_RST {val}\n'.encode('utf-8'))

    def set_rstb(val):
        ser.write(f'SET_RSTB {val}\n'.encode('utf-8'))

    def set_jitter_rms(val):
        ser.write(f'SET_JITTER_RMS {val}\n'.encode('utf-8'))

    def set_noise_rms(val):
        ser.write(f'SET_NOISE_RMS {val}\n'.encode('utf-8'))

    def set_prbs_eqn(val):
        ser.write(f'SET_PRBS_EQN {val}\n'.encode('utf-8'))

    def set_sleep(val):
        ser.write(f'SET_SLEEP {val}\n'.encode('utf-8'))

    def shift_ir(val, width):
        ser.write(f'SIR {val} {width}\n'.encode('utf-8'))

    def shift_dr(val, width):
        ser.write(f'SDR {val} {width}\n'.encode('utf-8'))
        return int(ser.readline().strip())

    def write_tc_reg(name, val):
        # specify address
        shift_ir(tc_cfg_addr, jtag_inst_width)
        shift_dr(get_reg_addr(name), tc_addr_width)

        # send data
        shift_ir(tc_cfg_data, jtag_inst_width)
        shift_dr(val, tc_bus_width)

        # specify "WRITE" operation
        shift_ir(tc_cfg_inst, jtag_inst_width)
        shift_dr(write, tc_op_width)

    def write_sc_reg(name, val):
        # specify address
        shift_ir(sc_cfg_addr, jtag_inst_width)
        shift_dr(get_reg_addr(name), sc_addr_width)

        # send data
        shift_ir(sc_cfg_data, jtag_inst_width)
        shift_dr(val, sc_bus_width)

        # specify "WRITE" operation
        shift_ir(sc_cfg_inst, jtag_inst_width)
        shift_dr(write, sc_op_width)

    def read_tc_reg(name):
        # specify address
        shift_ir(tc_cfg_addr, jtag_inst_width)
        shift_dr(get_reg_addr(name), tc_addr_width)

        # specify "READ" operation
        shift_ir(tc_cfg_inst, jtag_inst_width)
        shift_dr(read, tc_op_width)

        # get data
        shift_ir(tc_cfg_data, jtag_inst_width)
        return shift_dr(0, tc_bus_width)

    def read_sc_reg(name):
        # specify address
        shift_ir(sc_cfg_addr, jtag_inst_width)
        shift_dr(get_reg_addr(name), sc_addr_width)

        # specify "READ" operation
        shift_ir(sc_cfg_inst, jtag_inst_width)
        shift_dr(read, sc_op_width)

        # get data
        shift_ir(sc_cfg_data, jtag_inst_width)
        return shift_dr(0, sc_bus_width)

    def load_weight(
            d_idx,  # clog2(ffe_length)
            w_idx,  # 4 bits
            value,  # 10 bits
    ):
        print(
            f'Loading weight d_idx={d_idx}, w_idx={w_idx} with value {value}')

        # determine number of bits for d_idx
        d_idx_bits = int(ceil(log2(ffe_length)))

        # write wme_ffe_inst
        wme_ffe_inst = 0
        wme_ffe_inst |= d_idx & ((1 << d_idx_bits) - 1)
        wme_ffe_inst |= (w_idx & 0b1111) << d_idx_bits
        write_tc_reg('wme_ffe_inst', wme_ffe_inst)

        # write wme_ffe_data
        wme_ffe_data = value & 0b1111111111
        write_tc_reg('wme_ffe_data', wme_ffe_data)

        # pulse wme_ffe_exec
        write_tc_reg('wme_ffe_exec', 1)
        write_tc_reg('wme_ffe_exec', 0)

    def update_chan(coeff_tuples, offset=0):
        # put together the command
        args = ['UPDATE_CHAN', offset, len(coeff_tuples)]
        for coeff_tuple in coeff_tuples:
            args += coeff_tuple
        cmd = ' '.join(str(elem) for elem in args)
        ser.write((cmd + '\n').encode('utf-8'))

        # stall until confirmation is received
        assert ser.readline().decode('utf-8').strip() == 'OK', 'Serial error'

    # Initialize
    set_sleep(1)
    do_init()

    # Clear emulator reset.  A bit of delay is added just in case
    # the MT19937 generator is being used, because it takes awhile
    # to start up (LCG and LFSR start almost immediately)
    set_emu_rst(0)
    time.sleep(10e-3)

    # Reset JTAG
    print('Reset JTAG')
    do_reset()

    # Release other reset signals
    print('Release other reset signals')
    set_rstb(1)

    # Configure noise
    set_jitter_rms(int(round(jitter_rms * 1e13)))
    set_noise_rms(int(round(noise_rms * 1e4)))

    # Configure step response function
    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=get_dragonphy_real_type())
    chan_func = lambda t_vec: (1 - np.exp(-(t_vec - chan_delay) / chan_tau)
                               ) * np.heaviside(t_vec - chan_delay, 0)
    coeffs_bin = placeholder.get_coeffs_bin_fmt(chan_func)
    coeff_tuples = list(zip(*coeffs_bin))
    chunk_size = 32
    for k in range(len(coeff_tuples) // chunk_size):
        print(f'Updating channel at chunk {k}...')
        update_chan(coeff_tuples[(k * chunk_size):((k + 1) * chunk_size)],
                    offset=k * chunk_size)

    # Soft reset
    print('Soft reset')
    write_tc_reg('int_rstb', 1)
    write_tc_reg('en_inbuf', 1)
    write_tc_reg('en_gf', 1)
    write_tc_reg('en_v2t', 1)

    # read the ID
    print('Reading ID...')
    shift_ir(1, 5)
    id_result = shift_dr(0, 32)
    print(f'ID: {id_result}')

    # Set PFD offset
    print('Set PFD offset')
    for k in range(16):
        write_tc_reg(f'ext_pfd_offset[{k}]', 0)

    # Set the equation for the PRBS checker
    print('Setting the PRBS equation')
    write_sc_reg('prbs_eqn',
                 0x100002)  # matches equation used by prbs21 in DaVE

    # Configure PRBS checker
    print('Configure the PRBS checker')
    write_sc_reg('sel_prbs_mux', 1)  # "0" is ADC, "1" is FFE, "3" is BIST

    # Release the PRBS checker from reset
    print('Release the PRBS checker from reset')
    write_sc_reg('prbs_rstb', 1)

    # Set up the FFE
    dt = 1.0 / (16.0e9)
    coeff0 = 128.0 / (1.0 - exp(-dt / chan_tau))
    coeff1 = -128.0 * exp(-dt / chan_tau) / (1.0 - exp(-dt / chan_tau))
    for loop_var in range(16):
        for loop_var2 in range(ffe_length):
            if (loop_var2 == 0):
                # The argument order for load() is depth, width, value
                load_weight(loop_var2, loop_var, int(round(coeff0)))
            elif (loop_var2 == 1):
                load_weight(loop_var2, loop_var, int(round(coeff1)))
            else:
                load_weight(loop_var2, loop_var, 0)
        write_tc_reg(f'ffe_shift[{loop_var}]', 7)

    # Configure the CDR offsets
    print('Configure the CDR offsets')
    write_tc_reg('ext_pi_ctl_offset[0]', 0)
    write_tc_reg('ext_pi_ctl_offset[1]', 128)
    write_tc_reg('ext_pi_ctl_offset[2]', 256)
    write_tc_reg('ext_pi_ctl_offset[3]', 384)
    write_tc_reg('en_ext_max_sel_mux', 1)

    # Configure the retimer
    print('Configuring the retimer...')
    write_tc_reg('retimer_mux_ctrl_1', 0b1111111111111111)
    write_tc_reg('retimer_mux_ctrl_2', 0b1111111111111111)
    # write_tc_reg('retimer_mux_ctrl_1', 0b0000111111110000)
    # write_tc_reg('retimer_mux_ctrl_2', 0b1111000000000000)

    # Configure the CDR
    print('Configuring the CDR...')
    write_tc_reg('cdr_rstb', 0)
    write_tc_reg('Kp', 18)
    write_tc_reg('Ki', 0)
    write_tc_reg('invert', 1)
    write_tc_reg('en_freq_est', 0)
    write_tc_reg('en_ext_pi_ctl', 0)
    write_tc_reg('sel_inp_mux', 1)  # "0": use ADC output, "1": use FFE output

    # Re-initialize ordering
    print('Re-initialize ADC ordering')
    write_tc_reg('en_v2t', 0)
    write_tc_reg('en_v2t', 1)

    # Release the CDR from reset, then wait for it to lock
    # TODO: explore why it is not sufficient to pulse cdr_rstb low here
    # (i.e., seems that it must be set to zero while configuring CDR parameters
    print('Wait for the CDR to lock')
    write_tc_reg('cdr_rstb', 1)
    time.sleep(1.0)

    # Run PRBS test.  In order to get a conservative estimate for the throughput, the
    # test duration includes the time it takes to send JTAG commands to start and stop
    # the PRBS bit counter.  This is needed because the counter will start running partway
    # through the first JTAG command, and stop running partway through the second command.
    # Since we don't know exactly when this happens, a conservative estimate should include
    # the full time taken by the two JTAG commands.  The effect of their inclusion can be
    # minimized by running the test for a longer period.
    print('Run PRBS test')
    t_start = time.time()
    write_sc_reg('prbs_checker_mode', 2)
    time.sleep(prbs_test_dur)
    write_sc_reg('prbs_checker_mode', 3)
    t_stop = time.time()

    # Print out duration of PRBS test
    print(f'PRBS test took {t_stop-t_start} seconds.')

    # Read out PRBS test results
    print('Read out PRBS test results')

    err_bits = 0
    err_bits |= read_sc_reg('prbs_err_bits_upper')
    err_bits <<= 32
    err_bits |= read_sc_reg('prbs_err_bits_lower')
    print(f'err_bits: {err_bits}')

    total_bits = 0
    total_bits |= read_sc_reg('prbs_total_bits_upper')
    total_bits <<= 32
    total_bits |= read_sc_reg('prbs_total_bits_lower')
    print(f'total_bits: {total_bits}')

    print(f'BER: {err_bits/total_bits:e}')

    # check results
    print('Checking the results...')
    assert id_result == 497598771, 'ID mismatch'
    assert err_bits == 0, 'Bit error detected'
    assert total_bits > 100000, 'Not enough bits detected'

    # finish test
    print('OK!')
Exemple #6
0
def test_5():
    # download program
    ana = Analysis(input=str(THIS_DIR))
    ana.set_target(target_name='fpga')
    ana.program_firmware()
Exemple #7
0
def test_4():
    # build ELF
    ana = Analysis(input=str(THIS_DIR))
    ana.set_target(target_name='fpga')
    ana.build_firmware()
Exemple #8
0
def test_3():
    # build bitstream
    ana = Analysis(input=str(THIS_DIR))
    ana.set_target(target_name='fpga')
    ana.build()
Exemple #9
0
def main():
    result_file = r'./results/dummy.vcd'

    args = parse()
    ana = Analysis(input=root)                              # create analysis object to host prototyping project

    ana.set_target(target_name='fpga')                      # set the active target to 'fpga'

    if args.gen_bitstream:
        ana.gen_sources()                                   # generate functional models
        ana.build()                                         # generate bitstream for project

    ctrl_handle = ana.launch(debug=True)                    # start interactive control
    ctrl_handle.set_reset(1)                                # reset simulation
    ctrl_handle.setup_trace_unit(trigger_name='time',
                                 trigger_operator='gt',
                                 trigger_value=1e-9,
                                 sample_decimation=0,
                                 )           # config & arm trace unit

    #ctrl_handle.set_param('emu_ctrl_data', 10000000)
    #ctrl_handle.set_param('emu_ctrl_mode', 3)

    ctrl_handle.set_param('emu_ctrl_mode', 1)
    ctrl_handle.set_reset(0)                                # start simulation
    #sleep(0.1)
    time = ctrl_handle.get_emu_time()
    print(f'Paused at:{time}')
    ctrl_handle.sleep_emu(1e-6)
    #sleep(0.1)
    time = ctrl_handle.get_emu_time()
    print(f'Paused at:{time}')
    ctrl_handle.sleep_emu(1e-6)
    #sleep(0.1)
    time = ctrl_handle.get_emu_time()
    print(f'Paused at:{time}')
    ctrl_handle.sleep_emu(1e-6)
    #sleep(0.1)
    time = ctrl_handle.get_emu_time()
    print(f'Paused at:{time}')
    ctrl_handle.sleep_emu(1e-6)
    #sleep(0.1)
    time = ctrl_handle.get_emu_time()
    print(f'Paused at:{time}')

    ctrl_handle.wait_on_and_dump_trace(result_file=result_file)                    # wait till trace buffer is full and dump to result file
    ana.view(result_file=result_file)                                              # view waveform
Exemple #10
0
def test_2():
    ana = Analysis(input=root)
    ana.gen_sources()
    ana.set_target(target_name='fpga')
    ana.build()
Exemple #11
0
def test_1():
    ana = Analysis(input=root, simulator_name='icarus')
    ana.gen_sources()
    ana.set_target(target_name='sim')
    ana.simulate()
def main():
    args = parse()
    ana = Analysis(
        input=root)  # create analysis object to host prototyping project

    ana.gen_sources()  # generate functional models
    ana.set_target(target_name='fpga')  # set the active target to 'fpga'

    if args.gen_bitstream:
        ana.build()  # generate bitstream for project

    ctrl_handle = ana.launch(debug=True)  # start interactive control
    ctrl_handle.set_reset(1)  # reset simulation
    ctrl_handle.setup_trace_unit(trigger_name='time',
                                 trigger_operator='gt',
                                 trigger_value=5.5,
                                 sample_decimation=800,
                                 sample_count=16384)  # config & arm trace unit
    ctrl_handle.set_reset(0)  # start simulation
    ctrl_handle.wait_on_and_dump_trace(
    )  # wait till trace buffer is full and dump to result file
    ana.view()  # view waveform
class JtagTester:
    def __init__(self,
                 ser_port='/dev/ttyUSB0',
                 ffe_length=10,
                 bit_bang=False,
                 comm_style='uart',
                 jtag_sleep_us=1,
                 use_batch_mode=False,
                 print_mode='debug',
                 cdr_settling_time=0.1,
                 prbs_test_dur=0.1,
                 use_ffe=True):
        # save settings
        self.ser_port = ser_port
        self.bit_bang = bit_bang
        self.comm_style = comm_style
        self.ffe_length = ffe_length
        self.jtag_sleep_us = jtag_sleep_us
        self.use_batch_mode = use_batch_mode
        self.print_mode = print_mode
        self.cdr_settling_time = cdr_settling_time
        self.prbs_test_dur = prbs_test_dur
        self.use_ffe = use_ffe

        # initialize

        self.jtag_inst_width = 5
        self.sc_bus_width = 32
        self.sc_addr_width = 14

        self.tc_bus_width = 32
        self.tc_addr_width = 14

        self.sc_op_width = 2
        self.tc_op_width = 2

        self.sc_cfg_data = 8
        self.sc_cfg_inst = 9
        self.sc_cfg_addr = 10
        self.tc_cfg_data = 12
        self.tc_cfg_inst = 13
        self.tc_cfg_addr = 14

        self.read = 1
        self.write = 2

        # build register dictionary
        reg_list = json.load(
            open(get_file('build/all/jtag/reg_list.json'), 'r'))
        self.reg_dict = {elem['name']: elem['addresses'] for elem in reg_list}

        # list of transactions queued up
        self.batch_started = False
        self.uart_batch = []
        self.uart_bytes_total = 0
        self.reg_reads = 0
        self.reg_writes = 0

        # connect UART if desired
        if self.comm_style == 'uart':
            self.connect_serial()
        elif self.comm_style == 'vio':
            self.connect_vio()

    def start_batch(self):
        self.batch_started = True
        self.uart_batch = []

    def run_batch(self):
        # write all commands as a block
        all_cmds = [f'{cmd}\n' for cmd, _ in self.uart_batch]
        all_cmds = ''.join(all_cmds).encode('utf-8')
        self.ser.write(all_cmds)

        # keep track of how many bits were sent
        self.uart_bytes_total += len(all_cmds)

        # extract all of the deferred values
        for _, deferred_value in self.uart_batch:
            if deferred_value is not None:
                deferred_value.value = int(self.ser.readline().strip())

        # clear the batch flag
        self.batch_started = False

    def get_reg_addr(self, name):
        if '[' in name:
            lbracket = name.index('[')
            rbracket = name.index(']')
            base_name = name[:lbracket]
            index = int(name[(lbracket + 1):rbracket])
            return self.reg_dict[base_name][index]
        else:
            return self.reg_dict[name]

    def connect_serial(self):
        # connect to the CPU
        print('Connecting to the CPU...')
        self.ser = serial.Serial(port=self.ser_port, baudrate=115200)

    def connect_vio(self):
        # Create analysis object
        self.ana = Analysis(input=get_dir('tests/fpga_system_tests/emu_macro'))

        # Select the "FPGA" target (as opposed to CPU simulation)
        self.ana.set_target(target_name='fpga')

        # Start the interactive control
        self.ctrl = self.ana.launch(debug=False)

    # run a serial command
    def ser_cmd(self, cmd, expect_output=False):
        if self.batch_started:
            if expect_output:
                deferred_value = DeferredValue()
                self.uart_batch.append((cmd, deferred_value))
                return deferred_value
            else:
                self.uart_batch.append((cmd, None))
        else:
            # send the command
            cmd_bytes = f'{cmd}\n'.encode('utf-8')
            self.ser.write(cmd_bytes)

            # keep track of how many bytes have been sent
            self.uart_bytes_total += len(cmd_bytes)

            # read output if needed
            if expect_output:
                return int(self.ser.readline().strip())

    # functions
    def do_reset(self):
        if self.bit_bang:
            # initialize JTAG signals
            self.set_tdi(0)
            self.set_tck(0)
            self.set_tms(1)
            self.set_trst_n(0)
            self.cycle_tck()

            # de-assert reset
            self.set_trst_n(1)
            self.cycle_tck()

            # go to the IDLE state
            self.set_tms(1)
            self.cycle_tck()
            for _ in range(10):
                self.cycle_tck()

            self.set_tms(0)
            self.cycle_tck()
        else:
            self.ser_cmd('RESET')

    def do_init(self):
        if self.comm_style == 'uart':
            self.ser_cmd('INIT')
        elif self.comm_style == 'vio':
            # emulator control signal
            self.set_emu_rst(1)

            # JTAG-specific
            self.set_tdi(0)
            self.set_tck(0)
            self.set_tms(1)
            self.set_trst_n(0)

            # Other signals
            self.set_rstb(0)

    def cycle_tck(self):
        self.set_tck(1)
        self.set_tck(0)

    def set_emu_rst(self, val):
        if self.comm_style == 'uart':
            self.ser_cmd(f'SET_EMU_RST {val}')
        elif self.comm_style == 'vio':
            self.ctrl.set_reset(val)

    def set_rstb(self, val):
        if self.comm_style == 'uart':
            self.ser_cmd(f'SET_RSTB {val}')
        elif self.comm_style == 'vio':
            self.ctrl.set_param(name='rstb', value=val)

    def set_sleep(self, val):
        if self.comm_style == 'uart':
            self.ser_cmd(f'SET_SLEEP {val}')

    def set_tdi(self, val):
        if self.comm_style == 'uart':
            self.ser_cmd(f'SET_TDI {val}')
        elif self.comm_style == 'vio':
            self.ctrl.set_param(name='tdi', value=val)

    def set_tck(self, val):
        if self.comm_style == 'uart':
            self.ser_cmd(f'SET_TCK {val}')
        elif self.comm_style == 'vio':
            self.ctrl.set_param(name='tck', value=val)

    def set_tms(self, val):
        if self.comm_style == 'uart':
            self.ser_cmd(f'SET_TMS {val}')
        elif self.comm_style == 'vio':
            self.ctrl.set_param(name='tms', value=val)

    def set_trst_n(self, val):
        if self.comm_style == 'uart':
            self.ser_cmd(f'SET_TRST_N {val}')
        elif self.comm_style == 'vio':
            self.ctrl.set_param(name='trst_n', value=val)

    def get_tdo(self):
        if self.comm_style == 'uart':
            return self.ser_cmd(f'GET_TDO', expect_output=True)
        elif self.comm_style == 'vio':
            self.ctrl.refresh_param('vio_0_i')
            return int(self.ctrl.get_param('tdo'))
        else:
            return 0  # dummy value

    def shift_ir(self, val, width):
        if self.bit_bang:
            # Move to Select-DR-Scan state
            self.set_tms(1)
            self.cycle_tck()

            # Move to Select-IR-Scan state
            self.set_tms(1)
            self.cycle_tck()

            # Move to Capture IR state
            self.set_tms(0)
            self.cycle_tck()

            # Move to Shift-IR state
            self.set_tms(0)
            self.cycle_tck()

            # Remain in Shift-IR state and shift in inst_in.
            # Observe the TDO signal to read the x_inst_out
            for i in range(width - 1):
                self.set_tdi((val >> i) & 1)
                self.cycle_tck()

            # Shift in the last bit and switch to Exit1-IR state
            self.set_tdi((val >> (width - 1)) & 1)
            self.set_tms(1)
            self.cycle_tck()

            # Move to Update-IR state
            self.set_tms(1)
            self.cycle_tck()

            # Move to Run-Test/Idle state
            self.set_tms(0)
            self.cycle_tck()
            self.cycle_tck()
        else:
            self.ser_cmd(f'SIR {val} {width}')

    def shift_dr(self, val, width, expect_output=False):
        if self.bit_bang:
            # Move to Select-DR-Scan state
            self.set_tms(1)
            self.cycle_tck()

            # Move to Capture-DR state
            self.set_tms(0)
            self.cycle_tck()

            # Move to Shift-DR state
            self.set_tms(0)
            self.cycle_tck()

            # Remain in Shift-DR state and shift in data_in.
            # Observe the TDO signal to read the data_out
            if expect_output:
                retval = 0
            else:
                retval = None
            for i in range(width - 1):
                self.set_tdi((val >> i) & 1)
                if expect_output:
                    retval |= (self.get_tdo() << i)
                self.cycle_tck()

            # Shift in the last bit and switch to Exit1-DR state
            self.set_tdi((val >> (width - 1)) & 1)
            if expect_output:
                retval |= (self.get_tdo() << (width - 1))
            self.set_tms(1)
            self.cycle_tck()

            # Move to Update-DR state
            self.set_tms(1)
            self.cycle_tck()

            # Move to Run-Test/Idle state
            self.set_tms(0)
            self.cycle_tck()
            self.cycle_tck()

            # Return result
            return retval
        else:
            if expect_output:
                return self.ser_cmd(f'SDR {val} {width}', expect_output=True)
            else:
                self.ser_cmd(f'QSDR {val} {width}')

    def write_tc_reg(self, name, val):
        # update stats
        self.reg_writes += 1

        # print action
        self.print(f'Writing TC register {name} with value {val}...')

        # specify address
        self.shift_ir(self.tc_cfg_addr, self.jtag_inst_width)
        self.shift_dr(self.get_reg_addr(name), self.tc_addr_width)

        # send data
        self.shift_ir(self.tc_cfg_data, self.jtag_inst_width)
        self.shift_dr(val, self.tc_bus_width)

        # specify "WRITE" operation
        self.shift_ir(self.tc_cfg_inst, self.jtag_inst_width)
        self.shift_dr(self.write, self.tc_op_width)

    def write_sc_reg(self, name, val):
        # update stats
        self.reg_writes += 1

        # print action
        self.print(f'Writing SC register {name} with value {val}...')

        # specify address
        self.shift_ir(self.sc_cfg_addr, self.jtag_inst_width)
        self.shift_dr(self.get_reg_addr(name), self.sc_addr_width)

        # send data
        self.shift_ir(self.sc_cfg_data, self.jtag_inst_width)
        self.shift_dr(val, self.sc_bus_width)

        # specify "WRITE" operation
        self.shift_ir(self.sc_cfg_inst, self.jtag_inst_width)
        self.shift_dr(self.write, self.sc_op_width)

    def read_tc_reg(self, name):
        # update stats
        self.reg_reads += 1

        # print action
        self.print(f'Reading TC register {name}...')

        # specify address
        self.shift_ir(self.tc_cfg_addr, self.jtag_inst_width)
        self.shift_dr(self.get_reg_addr(name), self.tc_addr_width)

        # specify "READ" operation
        self.shift_ir(self.tc_cfg_inst, self.jtag_inst_width)
        self.shift_dr(self.read, self.tc_op_width)

        # get data
        self.shift_ir(self.tc_cfg_data, self.jtag_inst_width)
        return self.shift_dr(0, self.tc_bus_width, expect_output=True)

    def read_sc_reg(self, name):
        # update stats
        self.reg_reads += 1

        # print action
        self.print(f'Reading SC register {name}...')

        # specify address
        self.shift_ir(self.sc_cfg_addr, self.jtag_inst_width)
        self.shift_dr(self.get_reg_addr(name), self.sc_addr_width)

        # specify "READ" operation
        self.shift_ir(self.sc_cfg_inst, self.jtag_inst_width)
        self.shift_dr(self.read, self.sc_op_width)

        # get data
        self.shift_ir(self.sc_cfg_data, self.jtag_inst_width)
        return self.shift_dr(0, self.sc_bus_width, expect_output=True)

    def load_weight(
            self,
            d_idx,  # clog2(ffe_length)
            w_idx,  # 4 bits
            value,  # 10 bits
    ):
        self.print(
            f'Loading weight d_idx={d_idx}, w_idx={w_idx} with value {value}')

        # determine number of bits for d_idx
        d_idx_bits = int(ceil(log2(self.ffe_length)))

        # write wme_ffe_inst
        wme_ffe_inst = 0
        wme_ffe_inst |= d_idx & ((1 << d_idx_bits) - 1)
        wme_ffe_inst |= (w_idx & 0b1111) << d_idx_bits
        self.write_tc_reg('wme_ffe_inst', wme_ffe_inst)

        # write wme_ffe_data
        wme_ffe_data = value & 0b1111111111
        self.write_tc_reg('wme_ffe_data', wme_ffe_data)

        # pulse wme_ffe_exec
        self.write_tc_reg('wme_ffe_exec', 1)
        self.write_tc_reg('wme_ffe_exec', 0)

    def print(self, *args, **kwargs):
        if self.print_mode == 'debug':
            print(*args, **kwargs)

    def run_test(self):
        # record starting time
        start_time = time.time()

        # Start a batch if running in batch mode
        if self.use_batch_mode:
            self.start_batch()

        # Initialize
        if (self.comm_style == 'uart') and not self.bit_bang:
            self.set_sleep(self.jtag_sleep_us)
        self.do_init()

        # Clear emulator reset
        self.set_emu_rst(0)

        # Reset JTAG
        self.print('Reset JTAG')
        self.do_reset()

        # Release other reset signals
        self.print('Release other reset signals')
        self.set_rstb(1)

        # Soft reset
        self.print('Soft reset')
        self.write_tc_reg('int_rstb', 1)
        self.write_tc_reg('en_inbuf', 1)
        self.write_tc_reg('en_gf', 1)
        self.write_tc_reg('en_v2t', 1)

        # read the ID
        self.print('Reading ID...')
        self.shift_ir(1, 5)
        id_result = self.shift_dr(0, 32, expect_output=True)

        # Set PFD offset
        self.print('Set PFD offset')
        for k in range(16):
            self.write_tc_reg(f'ext_pfd_offset[{k}]', 0)

        # Configure PRBS checker
        self.print('Configure the PRBS checker')
        if self.use_ffe:
            # use FFE data
            self.write_tc_reg('sel_prbs_mux', 1)
        else:
            # use ADC data instead
            self.write_tc_reg('sel_prbs_mux', 0)

        # Release the PRBS checker from reset
        self.print('Release the PRBS checker from reset')
        self.write_tc_reg('prbs_rstb', 1)

        # Set up the FFE
        if self.use_ffe:
            dt = 1.0 / (16.0e9)
            tau = 25.0e-12
            coeff0 = 128.0 / (1.0 - exp(-dt / tau))
            coeff1 = -128.0 * exp(-dt / tau) / (1.0 - exp(-dt / tau))
            for loop_var in range(16):
                for loop_var2 in range(self.ffe_length):
                    if (loop_var2 == 0):
                        # The argument order for load() is depth, width, value
                        self.load_weight(loop_var2, loop_var,
                                         int(round(coeff0)))
                    elif (loop_var2 == 1):
                        self.load_weight(loop_var2, loop_var,
                                         int(round(coeff1)))
                    else:
                        self.load_weight(loop_var2, loop_var, 0)
                self.write_tc_reg(f'ffe_shift[{loop_var}]', 7)

        # Configure the CDR offsets
        self.print('Configure the CDR offsets')
        ext_pi_ctl_offset = [0, 128, 256, 384]
        for k in range(4):
            self.write_tc_reg(f'ext_pi_ctl_offset[{k}]', ext_pi_ctl_offset[k])
        self.write_tc_reg('en_ext_max_sel_mux', 1)

        # Configure the retimer
        self.print('Configuring the retimer...')
        self.write_tc_reg('retimer_mux_ctrl_1', 0xffff)
        self.write_tc_reg('retimer_mux_ctrl_2', 0xffff)

        # Configure the CDR
        self.print('Configuring the CDR...')
        self.write_tc_reg('cdr_rstb', 0)
        self.write_tc_reg('Kp', 18)
        self.write_tc_reg('Ki', 0)
        self.write_tc_reg('invert', 1)
        self.write_tc_reg('en_freq_est', 0)
        self.write_tc_reg('en_ext_pi_ctl', 0)
        if self.use_ffe:
            # use FFE data
            self.write_tc_reg('sel_inp_mux', 1)
        else:
            # use ADC data instead
            self.write_tc_reg('sel_inp_mux', 0)

        # Re-initialize ordering
        self.print('Re-initialize ADC ordering')
        self.write_tc_reg('en_v2t', 0)
        self.write_tc_reg('en_v2t', 1)

        # Release the CDR from reset, then wait for it to lock
        # TODO: explore why it is not sufficient to pulse cdr_rstb low here
        # (i.e., seems that it must be set to zero while configuring CDR parameters
        self.print('Wait for the CDR to lock')
        self.write_tc_reg('cdr_rstb', 1)
        if self.use_batch_mode:
            self.run_batch()
        time.sleep(self.cdr_settling_time)
        if self.use_batch_mode:
            self.start_batch()

        # Run PRBS test
        self.print('Run PRBS test')
        self.write_tc_reg('prbs_checker_mode', 2)
        if self.use_batch_mode:
            self.run_batch()
        time.sleep(self.prbs_test_dur)
        if self.use_batch_mode:
            self.start_batch()
        self.write_tc_reg('prbs_checker_mode', 3)

        # Read out PRBS test results
        self.print('Read out PRBS test results')

        self.print('Reading err_bits...')
        err_bits = 0
        err_bits = err_bits | self.read_sc_reg('prbs_err_bits_upper')
        err_bits = err_bits << 32
        err_bits = err_bits | self.read_sc_reg('prbs_err_bits_lower')

        self.print('Reading total_bits...')
        total_bits = 0
        total_bits = total_bits | self.read_sc_reg('prbs_total_bits_upper')
        total_bits = total_bits << 32
        total_bits = total_bits | self.read_sc_reg('prbs_total_bits_lower')

        # Run batch if needed, converting DeferredValue objects to integers afterwards
        if self.use_batch_mode:
            self.run_batch()
            id_result = id_result.value
            err_bits = err_bits.value
            total_bits = total_bits.value

        # Measure stop time
        stop_time = time.time()
        print(f'Test took {stop_time - start_time} seconds.')
        print(f'Register writes: {self.reg_writes}')
        print(f'Register reads: {self.reg_reads}')
        if self.comm_style == 'uart':
            print(f'Total bytes transmitted (UART): {self.uart_bytes_total}')

        # Print results
        print(f'id_result: {id_result}')
        print(f'err_bits: {err_bits}')
        print(f'total_bits: {total_bits}')

        # Check results
        print('Checking the results...')
        assert id_result == 497598771, 'ID mismatch'
        assert err_bits == 0, 'Bit error detected'
        assert total_bits > 100000, 'Not enough bits detected'

        # Finish test
        print('OK!')
reg_list = json.load(open(get_file('build/all/jtag/reg_list.json'), 'r'))
reg_dict = {elem['name']: elem['addresses'] for elem in reg_list}
arr_pat = re.compile(r'([a-zA-Z_0-9]+)+\[(\d+)\]')
def get_reg_addr(name):
    m = arr_pat.match(name)
    if m:
        name = m.groups()[0]
        index = int(m.groups()[1])
        return reg_dict[name][index]
    else:
        return reg_dict[name]

# connect to the CPU
print('Connecting to the CPU...')
ana = Analysis(input=str(get_file(f'tests/fpga_system_tests/{mode}')))
ana.set_target(target_name='fpga')
ctrl = ana.launch()
ser = ctrl.ctrl_handler

# functions
def do_reset():
    ser.write('RESET\n'.encode('utf-8'))

def do_init():
    ser.write('INIT\n'.encode('utf-8'))

def set_emu_rst(val):
    ser.write(f'SET_EMU_RST {val}\n'.encode('utf-8'))

def set_rstb(val):