diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 44161240..82bc9896 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8] + python-version: [3.11] steps: - uses: actions/checkout@v2 - name: Set up Python ${{matrix.python-version}} @@ -31,7 +31,7 @@ jobs: tests: name: Python ${{matrix.python-version}} | ${{matrix.sim}} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: SIM: ${{matrix.sim}} @@ -39,24 +39,24 @@ jobs: fail-fast: false matrix: include: - - sim: icarus - python-version: 3.7 + python-version: '3.11' - sim: icarus - python-version: 3.8 + python-version: '3.10' - sim: verilator - sim-version: v4.106 - python-version: 3.8 + sim-version: v5.020 + python-version: '3.10' pytest-marker: "-m verilator" steps: - uses: actions/checkout@v1 - - uses: conda-incubator/setup-miniconda@v2 + - name: Set up Anaconda ${{matrix.python-version}} + uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true - python-version: ${{ matrix.python-version }} + python-version: ${{matrix.python-version}} - name: Install Conda dependencies shell: bash -l {0} @@ -67,12 +67,12 @@ jobs: - name: Install Python dependencies shell: bash -l {0} run: | - pip install pyvisa pyvisa-sim pytest coveralls pytest-cov cocotb==1.5.2 + pip install pyvisa pyvisa-sim pytest coveralls pytest-cov cocotb>=1.8.1 cocotb-bus - name: Install Verilator if: matrix.sim == 'verilator' run: | - sudo apt install -y --no-install-recommends make g++ perl python3 autoconf flex bison libfl2 libfl-dev zlibc zlib1g zlib1g-dev + sudo apt install -y --no-install-recommends make g++ help2man perl autoconf flex bison libfl2 libfl-dev zlib1g zlib1g-dev git clone https://github.com/verilator/verilator.git -b ${{matrix.sim-version}} cd verilator autoconf diff --git a/basil/HL/JtagMaster.py b/basil/HL/JtagMaster.py index e3b5adfa..b270f56a 100644 --- a/basil/HL/JtagMaster.py +++ b/basil/HL/JtagMaster.py @@ -102,7 +102,7 @@ def scan_ir(self, data, readback=True): bit_number = self._test_input(data) self.SIZE = bit_number - if type(data[0]) == BitLogic: + if isinstance(data[0], BitLogic): data_byte = self._bitlogic2bytes(data) else: data_byte = self._raw_data2bytes(data) @@ -135,7 +135,7 @@ def scan_dr(self, data, readback=True, word_size=None): self.WORD_COUNT = bit_number // word_size self.SIZE = word_size - if type(data[0]) == BitLogic: + if isinstance(data[0], BitLogic): data_byte = self._bitlogic2bytes(data) else: data_byte = self._raw_data2bytes(data) diff --git a/basil/HL/SentioProber.py b/basil/HL/SentioProber.py index 65adf683..752e8447 100644 --- a/basil/HL/SentioProber.py +++ b/basil/HL/SentioProber.py @@ -48,9 +48,13 @@ def goto_first_die(self): def get_die(self): ''' Get chip index ''' - print("Check if return order of col and row is correct and consistent with other pc drivers.") - values = self._intf.query("map:die:get_current_index").split(",") - print(int(values[-2]), int(values[-1])) + reply = self._intf.query("map:die:get_current_index").strip() + if reply == '0:' or reply == '': + reply = self._intf.query("map:die:get_current_index") + + values = reply[2:].split(',') + + return (int(values[0]), int(values[1])) def contact(self): ''' Move chuck to contact z position''' diff --git a/basil/HL/SignatoneProber.py b/basil/HL/SignatoneProber.py index b0c19003..e93b8b02 100644 --- a/basil/HL/SignatoneProber.py +++ b/basil/HL/SignatoneProber.py @@ -39,9 +39,11 @@ def get_die(self): reply = self._intf.query('GETCR') else: break + reply = re.sub(r'[a-zA-Z]', r'', reply) values = reply.split(',') - return (abs(int(values[0])), abs(int(values[1]))) + + return (int(values[0]), int(values[1])) def contact(self): ''' Move chuck to contact z position''' diff --git a/basil/HL/keithley_2400.yaml b/basil/HL/keithley_2400.yaml index 8f584ca5..dd24d887 100644 --- a/basil/HL/keithley_2400.yaml +++ b/basil/HL/keithley_2400.yaml @@ -6,8 +6,10 @@ identifier : KEITHLEY INSTRUMENTS INC.,MODEL 2400 on : OUTP ON off : OUTP OFF get_on: OUTP? +get_source_current: SOUR:CURR? get_current : SENSE:FUNC 'CURR';:READ? set_current : SOUR:CURR +get_source_voltage: SOUR:VOLT? set_voltage : SOUR:VOLT get_voltage : SENSE:FUNC 'VOLT';:READ? set_current_limit : SENS:CURR:PROT @@ -23,3 +25,9 @@ get_autorange : SOUR:CURR:RANG:AUTO? four_wire_on: SYST:RSEN ON four_wire_off: SYST:RSEN OFF get_remote_sense: SYST:RSEN ? +# Special keyword for formatting query results to allow direct conversions to numeric types (e.g. float(get_current())) +__scpi_query_fmt: + fmt_sep: ',' + fmt_method: + get_voltage: '{0}' + get_current: '{1}' diff --git a/basil/HL/keithley_2634b.yaml b/basil/HL/keithley_2634b.yaml index 813d8e6a..5bceb184 100644 --- a/basil/HL/keithley_2634b.yaml +++ b/basil/HL/keithley_2634b.yaml @@ -6,22 +6,99 @@ identifier : Keithley Instruments Inc., Model 2634B channel 1: on : smua.source.output = 1 off : smua.source.output = 0 + get_on : print(smua.source.output) get_current : print(smua.measure.i()) - set_voltage : smua.source.levelv = + set_current : smua.source.leveli = + get_source_current : print(smua.source.leveli) get_voltage : print(smua.measure.v()) + set_voltage : smua.source.levelv = + get_source_voltage : print(smua.source.levelv) + set_current_limit : smua.source.limiti = + get_current_limit : print(smua.source.limiti) + set_voltage_limit : smua.source.limitv = + get_voltage_limit : print(smua.source.limitv) + source_volt : smua.source.func = smua.OUTPUT_DCVOLTS + source_current : smua.source.func = smua.OUTPUT_DCAMPS + get_source_mode : print(smua.source.func) + set_voltage_range: smua.source.rangev = + get_voltage_range: print(smua.source.rangev) + set_voltage_autorange_on: smua.source.autorangev = smua.AUTORANGE_ON + set_voltage_autorange_off: smua.source.autorangev = smua.AUTORANGE_OFF + set_current_range: smua.source.rangei = + get_current_range: print(smua.source.rangei) + set_current_autorange_on: smua.source.autorangei = smua.AUTORANGE_ON + set_current_autorange_off: smua.source.autorangei = smua.AUTORANGE_OFF + four_wire_on: smua.sense = smua.SENSE_REMOTE + four_wire_off: smua.sense = smua.SENSE_LOCAL + set_current_sense_range: smua.measure.rangei = + get_current_sense_range: print(smua.measure.rangei) + set_voltage_sense_range: smua.measure.rangev = + get_voltage_sense_range: print(smua.measure.rangev) set_mode_measure_current : display.smua.measure.func = display.MEASURE_DCAMPS channel 2: on : smub.source.output = 1 off : smub.source.output = 0 + get_on : print(smub.source.output) get_current : print(smub.measure.i()) - set_voltage : smub.source.levelv = + set_current : smub.source.leveli = + get_source_current : print(smub.source.leveli) get_voltage : print(smub.measure.v()) + set_voltage : smub.source.levelv = + get_source_voltage : print(smub.source.levelv) + set_current_limit : smub.source.limiti = + get_current_limit : print(smub.source.limiti) + set_voltage_limit : smub.source.limitv = + get_voltage_limit : print(smub.source.limitv) + source_volt : smub.source.func = smub.OUTPUT_DCVOLTS + source_current : smub.source.func = smub.OUTPUT_DCAMPS + get_source_mode : print(smub.source.func) + set_voltage_range: smub.source.rangev = + get_voltage_range: print(smub.source.rangev) + set_voltage_autorange_on: smub.source.autorangev = smub.AUTORANGE_ON + set_voltage_autorange_off: smub.source.autorangev = smub.AUTORANGE_OFF + set_current_range: smub.source.rangei = + get_current_range: print(smub.source.rangei) + set_current_autorange_on: smub.source.autorangei = smub.AUTORANGE_ON + set_current_autorange_off: smub.source.autorangei = smub.AUTORANGE_OFF + four_wire_on: smub.sense = smub.SENSE_REMOTE + four_wire_off: smub.sense = smub.SENSE_LOCAL + set_current_sense_range: smub.measure.rangei = + get_current_sense_range: print(smub.measure.rangei) + set_voltage_sense_range: smub.measure.rangev = + get_voltage_sense_range: print(smub.measure.rangev) set_mode_measure_current : display.smub.measure.func = display.MEASURE_DCAMPS + on : smua.source.output = 1 off : smua.source.output = 0 +get_on : print(smua.source.output) get_current : print(smua.measure.i()) -set_voltage : smua.source.levelv = +set_current : smua.source.leveli = +get_source_current : print(smua.source.leveli) get_voltage : print(smua.measure.v()) +set_voltage : smua.source.levelv = +get_source_voltage : print(smua.source.levelv) +set_current_limit : smua.source.limiti = +get_current_limit : print(smua.source.limiti) +set_voltage_limit : smua.source.limitv = +get_voltage_limit : print(smua.source.limitv) +source_volt : smua.source.func = smua.OUTPUT_DCVOLTS +source_current : smua.source.func = smua.OUTPUT_DCAMPS +get_source_mode : print(smua.source.func) +set_voltage_range: smua.source.rangev = +get_voltage_range: print(smua.source.rangev) +set_voltage_autorange_on: smua.source.autorangev = smua.AUTORANGE_ON +set_voltage_autorange_off: smua.source.autorangev = smua.AUTORANGE_OFF +set_current_range: smua.source.rangei = +get_current_range: print(smua.source.rangei) +set_current_autorange_on: smua.source.autorangei = smua.AUTORANGE_ON +set_current_autorange_off: smua.source.autorangei = smua.AUTORANGE_OFF +four_wire_on: smua.sense = smua.SENSE_REMOTE +four_wire_off: smua.sense = smua.SENSE_LOCAL +set_current_sense_range: smua.measure.rangei = +get_current_sense_range: print(smua.measure.rangei) +set_voltage_sense_range: smua.measure.rangev = +get_voltage_sense_range: print(smua.measure.rangev) set_mode_measure_current : display.smua.measure.func = display.MEASURE_DCAMPS + diff --git a/basil/TL/Socket.py b/basil/TL/Socket.py index 76a9b3d3..e2585877 100644 --- a/basil/TL/Socket.py +++ b/basil/TL/Socket.py @@ -37,7 +37,7 @@ def close(self): self._sock.close() def write(self, data): - if type(data) == bytes: + if isinstance(data, bytes): cmd = data else: cmd = data.encode(self.encoding) diff --git a/basil/firmware/modules/utils/CG_MOD_neg.v b/basil/firmware/modules/utils/CG_MOD_neg.v index 9ac7212e..865825b8 100644 --- a/basil/firmware/modules/utils/CG_MOD_neg.v +++ b/basil/firmware/modules/utils/CG_MOD_neg.v @@ -18,7 +18,7 @@ input ck_in,enable; output ck_out; reg enl; -always @(ck_in or enable) +always_latch if (ck_in) enl = enable; assign ck_out = ck_in | ~enl; diff --git a/basil/firmware/modules/utils/CG_MOD_pos.v b/basil/firmware/modules/utils/CG_MOD_pos.v index eb6eb203..7a5667b1 100644 --- a/basil/firmware/modules/utils/CG_MOD_pos.v +++ b/basil/firmware/modules/utils/CG_MOD_pos.v @@ -18,7 +18,7 @@ wire ck_inb; reg enl; assign ck_inb = ~ck_in; -always @(ck_inb or enable) +always_latch if (ck_inb) enl = enable; assign ck_out = ck_in & enl; diff --git a/basil/utils/USBBinds.py b/basil/utils/USBBinds.py new file mode 100644 index 00000000..ea4960d3 --- /dev/null +++ b/basil/utils/USBBinds.py @@ -0,0 +1,245 @@ +import os + +from pathlib import Path + +from six import string_types + +import pyvisa + +import ruamel.yaml + + +def query_identification(rm, resource, baud_rate, read_termination=None, write_termination=None, timeout=1000 * 5): + """ + Queries the identification of the instrument connected via USB. + + Args: + rm (pyvisa.ResourceManager): The pyvisa resource manager. + resource (str): The resource name or address of the instrument. + baud_rate (int): The baud rate for communication with the instrument. + read_termination (str, optional): The read termination character(s). Defaults to "\n". + write_termination (str, optional): The write termination character(s). Defaults to "\n". + timeout (int, optional): The timeout value in milliseconds. Defaults to 5000. + + Returns: + str: The identification string of the instrument. + + """ + inst = rm.open_resource(resource) + inst.timeout = timeout + inst.baud_rate = baud_rate + inst.read_termination = read_termination + inst.write_termination = write_termination + + return inst.query("*IDN?", delay=0.5) + + +def find_usb_binds(rm, log, + instruments, + binds_to_skip=[], + memorized_binds=[], + timeout=1000 * 4, + verbose=False, + ): + """ + Finds the USB bind for each instrument in the given list of instruments. + + Args: + rm (pyvisa.ResourceManager): The pyvisa resource manager. + log (logging.Logger): The logger object for logging messages. + instruments (list): List of dictionaries representing the instruments. + Each dictionary should contain the following keys: + - 'baud_rate': The baud rate for the instrument. + - 'read_termination': The read termination character for the instrument. + - 'write_termination' (optional): The write termination character for the instrument. + - 'identification': The identification string for the instrument. + binds_to_skip (list, optional): List of binds to skip during the search. + Defaults to an empty list. + memorized_binds (list, optional): List of memorized binds. + Defaults to an empty list. + timeout (int, optional): Timeout value in milliseconds. + Defaults to 4000. + verbose (bool, optional): Flag indicating whether to log verbose messages. + Defaults to False. + + Returns: + dict: A dictionary mapping the identification strings of the instruments + to their corresponding USB binds. + """ + skip_binds = binds_to_skip + [ + str(Path(f"/dev/{bind}").resolve()) for bind in binds_to_skip + ] + + results = {} + + for instrument in instruments: + # try first ASRL port + if instrument.get("port") and 'ASRL' not in instrument.get("port"): + instrument["port"] = f'ASRL{instrument["port"]}::INSTR' + resources = (instrument["port"],) + rm.list_resources() if instrument.get("port") else rm.list_resources() + + for res in resources: + if "USB" not in res: # Only search for USB devices + continue + + if any(bind in res for bind in skip_binds): + if verbose: + log.info(f"Skipping USB bind {res}") + continue + + try: + if verbose: + log.info(f"Trying {res} with baud rate {instrument['baud_rate']}") + + if any(res in bind for bind in memorized_binds): + if verbose: + log.info(f"Found memorized bind {res}") + result = memorized_binds[res] + else: + result = query_identification(rm, res, instrument['baud_rate'], instrument['read_termination'], instrument['write_termination'], timeout=timeout, verbose=verbose) + + memorized_binds.append({res, result}) + + if verbose: + log.info(f"Found {result.strip()}") + + if result.lower().strip() in [inst["identification"].lower().strip() for inst in instruments]: + substring = res.split("/")[2].split("::")[0] + + log.info(f"Matched instrument {instrument['identification']} to /dev/{str(substring)}") + skip_binds.append(f"/dev/{str(substring)}") + + results[result.lower().strip()] = f"/dev/{str(substring)}" + + if len(results) == len(instruments): + return results + + except pyvisa.VisaIOError: + pass + + return results + + +def get_baudrate(dictionary): + """ + Gets the baud rate from the given dictionary. + + Args: + dict (dict): The dictionary to get the baud rate from. + + Returns: + int: The baud rate. + """ + if "baud_rate" in dictionary.keys(): + return dictionary["baud_rate"] + elif "baudrate" in dictionary.keys(): + return dictionary["baudrate"] + else: + return 9600 + + +def load_yaml_with_comments(conf): + """ + Load YAML configuration file with comments. + + Args: + conf: The YAML configuration file path or a file-like object containing YAML content. + + Returns: + dict: A dictionary containing the parsed YAML configuration. + + """ + conf_dict = {} + + if not conf: + pass + elif isinstance(conf, string_types): # parse the first YAML document in a stream + yaml = ruamel.yaml.YAML() + if os.path.isfile(conf): + with open(conf, 'r') as f: + yaml = ruamel.yaml.YAML() + conf_dict.update(yaml.load(f)) + else: # YAML string + try: + conf_dict.update(yaml.load(conf)) + except ValueError: # invalid path/filename + raise IOError("File not found: %s" % conf) + elif hasattr(conf, 'read'): # parse the first YAML document in a stream + yaml = ruamel.yaml.YAML() + conf_dict.update(yaml.load(conf)) + conf.close() + else: # conf is already a dict + conf_dict.update(conf) + + return conf_dict + + +def modify_basil_config(conf, log, skip_binds=[], save_modified=None, verbose=False): + """ + Modifies the basil configuration file by finding USB binds for devices. + + Args: + conf (dict): The basil configuration dictionary. + log: The logger object for logging messages. + skip_binds (list, optional): List of USB binds to skip. Defaults to []. + save_modified (str, optional): Path to save the modified basil configuration file. Defaults to None. + verbose (bool, optional): Flag to enable verbose logging. Defaults to False. + + Returns: + dict: The modified basil configuration dictionary. + """ + log.info("Trying to find USB binds for devices in basil configuration file") + + rm = pyvisa.ResourceManager() + + instruments = [] + insts_idx_map = {} + + # Iterate over transfer layers in the configuration + for i, tf in enumerate(conf["transfer_layer"]): + if ( + "identification" not in tf["init"].keys() + or "read_termination" not in tf["init"].keys() + or not any(e in tf["init"].keys() for e in ["baud_rate", "baudrate"]) + ): + if verbose: + log.debug(f"Skipping {tf['type']} transfer layer with name {tf['name']}") + continue + + instrument = tf["init"]["identification"] + baud_rate = get_baudrate(tf["init"]) + read_termination = tf["init"]["read_termination"] + write_termination = tf["init"]["write_termination"] if "write_termination" in tf["init"].keys() else "\n" + port = tf["init"]["port"] if "port" in tf["init"].keys() else None + + instruments.append({ + "identification": instrument, + "baud_rate": baud_rate, + "read_termination": read_termination, + "write_termination": write_termination, + "port": port, + }) + + insts_idx_map[instrument.lower().strip()] = i + + found_binds = find_usb_binds(rm, log=log, instruments=instruments, binds_to_skip=skip_binds, verbose=verbose) + + for inst in found_binds.keys(): + if found_binds[inst] is None: + raise LookupError(f"Could not find USB bind for {inst.title().replace('_', '')}") + + if conf["transfer_layer"][insts_idx_map[inst]]["type"].lower() == "serial": + conf["transfer_layer"][insts_idx_map[inst]]["init"]["port"] = found_binds[inst] + elif conf["transfer_layer"][insts_idx_map[inst]]["type"].lower() == "visa": + conf["transfer_layer"][insts_idx_map[inst]]["init"]["resource_name"] = f"ASRL{found_binds[inst]}::INSTR" + + if save_modified is not None: + yaml = ruamel.yaml.YAML() + log.info(f'Saving modified periphery file: {save_modified}') + with open(save_modified, 'w') as f: + yaml.dump(conf, f) + + for inst in found_binds.keys(): + del conf["transfer_layer"][insts_idx_map[inst]]["init"]["identification"] + + return conf diff --git a/basil/utils/sim/utils.py b/basil/utils/sim/utils.py index b3b46460..23f63928 100644 --- a/basil/utils/sim/utils.py +++ b/basil/utils/sim/utils.py @@ -61,7 +61,7 @@ def cocotb_makefile(sim_files, top_level='tb', test_module='basil.utils.sim.Test #export COCOTB=$(shell SPHINX_BUILD=1 python -c "import cocotb; import os; print(os.path.dirname(os.path.dirname(os.path.abspath(cocotb.__file__))))") #export PYTHONPATH=$(shell python -c "from distutils import sysconfig; print(sysconfig.get_python_lib())"):$(COCOTB) #export LD_LIBRARY_PATH=/lib/x86_64-linux-gnu:$(PYTHONLIBS) -export PYTHONHOME=$(shell python -c "from distutils.sysconfig import get_config_var; print(get_config_var('prefix'))") +#export PYTHONHOME=$(shell python -c "from distutils.sysconfig import get_config_var; print(get_config_var('prefix'))") ifeq ($(SIM),questa) EXTRA_ARGS += $(NOT_ICARUS_DEFINES) diff --git a/examples/lab_devices/keithley2634b.yaml b/examples/lab_devices/keithley2634b.yaml new file mode 100644 index 00000000..e85219f9 --- /dev/null +++ b/examples/lab_devices/keithley2634b.yaml @@ -0,0 +1,15 @@ +transfer_layer: + - name: Serial + type: Serial + init: + port: /dev/ttyUSB5 + read_termination: "\r\n" + baudrate: 19200 + timeout: 5 + +hw_drivers: + - name: Sourcemeter1 + type: scpi + interface: Serial + init: + device: Keithley 2634B \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bd34bcf8..34a02c7b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ numpy pyyaml bitarray>=2.0.0 six +ruamel.yaml \ No newline at end of file diff --git a/tests/test_SimGpio.v b/tests/test_SimGpio.v index 4f76ee54..263ebb80 100644 --- a/tests/test_SimGpio.v +++ b/tests/test_SimGpio.v @@ -53,12 +53,12 @@ localparam GPIO2_HIGHADDR = 16'h001f; assign BUS_DATA_OUT = BUS_DATA_OUT_1 | BUS_DATA_OUT_2; `endif -/* verilator lint_off UNOPT */ +/* verilator lint_off UNOPTFLAT */ wire [23:0] IO; assign IO[15:8] = IO[7:0]; assign IO[23:20] = IO[19:16]; -/* verilator lint_on UNOPT */ +/* verilator lint_on UNOPTFLAT */ `ifndef BASIL_SBUS gpio #(