Source Measure Units (SMUs)#

SMUs are precision instruments that can source current or voltage, commonly supplying a constant amount of power to flux tunable qubits. The Keysight M9614A and M9615A are PXIe five-channel precision SMUs. SMU integrated with QCS can support sourcing current and voltage up to 30 V / 60 mA with resolution down to 6 μV / 100 pA for each channel.

Port 1

Port 2

Port 3

Port 4

Port 5

Maximum Voltage / Maximum Current

30V / 60mA

30V / 60mA

30V / 60mA

30V / 60mA

30V / 60mA

Resolution Voltage / Resolution Current

6µV / 100pA

6µV / 100pA

6µV / 100pA

6µV / 100pA

6µV / 100pA

SMU can source precision voltage / current at each port on user request. Setting of voltage / current is done in steps (ramp) instead of one shot. This is to avoid sudden change in SMU port output which is expected to be connected to quantum circuits. By default, 100ms is the time taken for any ramp in voltage / current unless user manually requests for a faster ramp rate / slower ramp rate. There is also a limit set for faster ramp rate (minimum 1ms) / slower ramp rate (maximum 1 sec).

Ramping Modes:

  1. Auto-Ramp Mode

  1. Ramping is fixed at 100 ms duration with 100 steps for Voltage >= 600uV / Current >= 10nA

  2. Each step is executed in 1 ms.

  3. Step size depends on the requested target value.

  4. If Voltage < 600uV / Current < 10nA, number of steps will be reduced with step value as minimum resolution value (6uV/100pA)

  1. Manual-Ramp Mode

  1. User specifies a manual ramp rate.

  2. Ramp step duration: minimum 1 ms, maximum 1000 ms (1 sec).

  3. Ramp steps: minimum 1, maximum 1000.

  4. Ramp duration is auto adjusted to keep within 1 ms >= duration <= 1 sec.

This document will explain how to control SMU from the QCS SDK.

There are two categories of approaches that can be used to set the SMU voltage or current.

  1. Controlling SMUs directly without QCS program

    One approach is to set SMU voltage or current directly through the HclBackend class. This does not require the submission of a program.

    The set_smu_current method

    This sets constant current on a single SMU port without ramping rate

    backend = qcs.HclBackend(channel_mapper=mapper)
    address = qcs.Address(chassis= 1, slot= 12, channel= 1)
    backend.set_smu_current(address, constant_current=1e-6)
    

    This sets constant current on a single SMU port with ramping rate

    backend = qcs.HclBackend(channel_mapper=mapper)
    address = qcs.Address(chassis= 1, slot= 12, channel= 1)
    backend.set_smu_current(address, constant_current=1.5e-6, ramping_rate=2e-6)
    # Note: If user attempted total ramping time of an operation is more than one second, then the ramp rate will be determined by the backend.
    

    It is possible to take in multiple addresses to set the currents simultaneously.

    This sets constant current on multiple SMU ports without ramping rate

    backend = qcs.HclBackend(channel_mapper=mapper)
    address_1 = qcs.Address(chassis= 1, slot= 12, channel= 1)
    address_2 = qcs.Address(chassis= 1, slot= 12, channel= 2)
    addresses = [address_1, address_2]
    backend.set_smu_current(addresses, constant_current=[1.1e-6, 1.2e-6])
    

    This sets constant current on multiple SMU ports with ramping rate

    backend = qcs.HclBackend(channel_mapper=mapper)
    address_1 = qcs.Address(chassis= 1, slot= 12, channel= 1)
    address_2 = qcs.Address(chassis= 1, slot= 12, channel= 2)
    addresses = [address_1, address_2]
    backend.set_smu_current(addresses, constant_current=[1e-6, 1.5e-6], ramping_rate=[2e-6,3e-6])
    # Note: If user attempted total ramping time of an operation is more than one second, then the ramp rate will be determined by the backend.
    # Note: if multiple addresses are supplied as a list, then multiple ramping rate shall also be a list corresponding to each address.
    

    The get_smu_current method

    This gets the active current on a single SMU port

    backend = qcs.HclBackend(channel_mapper=mapper)
    address = qcs.Address(chassis= 1, slot= 12, channel= 1)
    current_value = backend.get_smu_current(address)
    

    The set_smu_voltage method

    This sets constant voltage on a single SMU port without ramping rate

    backend = qcs.HclBackend(channel_mapper=mapper)
    address = qcs.Address(chassis= 1, slot= 12, channel= 1)
    backend.set_smu_voltage(address, constant_voltage=1.0)
    

    This sets constant voltage on a single SMU port with ramping rate

    backend = qcs.HclBackend(channel_mapper=mapper)
    address = qcs.Address(chassis= 1, slot= 12, channel= 1)
    backend.set_smu_voltage(address, constant_voltage=2.0, ramping_rate=2.0)
    # Note: If user attempted total ramping time of an operation is more than one second, then the ramp rate will be determined by the backend.
    

    Likewise this can also set voltages for multiple addresses at once.

    This sets constant voltage on multiple SMU ports without ramping rate

    backend = qcs.HclBackend(channel_mapper=mapper)
    address_1 = qcs.Address(chassis= 1, slot= 12, channel= 1)
    address_2 = qcs.Address(chassis= 1, slot= 12, channel= 2)
    addresses = [address_1, address_2]
    backend.set_smu_voltage(addresses, constant_voltage=[1.1, 1.2])
    

    This sets constant voltage on multiple SMU ports with ramping rate

    backend = qcs.HclBackend(channel_mapper=mapper)
    address_1 = qcs.Address(chassis= 1, slot= 12, channel= 1)
    address_2 = qcs.Address(chassis= 1, slot= 12, channel= 2)
    addresses = [address_1, address_2]
    backend.set_smu_voltage(addresses, constant_voltage=[2.77, 3.77], ramping_rate=[10, 5])
    # Note: If user attempted total ramping time of an operation is more than one second, then the ramp rate will be determined by the backend.
    

    The get_smu_voltage method

    This gets the active voltage oon a single SMU port

    backend = qcs.HclBackend(channel_mapper=mapper)
    address = qcs.Address(chassis= 1, slot= 12, channel= 1)
    voltage_value = backend.get_smu_voltage(address)
    

    The calibrate_smu_channels method

    This calibrates all SMU ports in the system.

    Warning: The SMU will generate current during calibration. Disconnect all SMU outputs from your device before running this command.

    backend = qcs.HclBackend(channel_mapper=mapper)
    backend.calibrate_smu_channels()
    

    The turn_off_smu method

    This is to turn the SMU channel completely off

    backend = qcs.HclBackend(channel_mapper=mapper)
    address = qcs.Address(chassis= 1, slot= 12, channel= 1)
    backend.turn_off_smu(address)
    
  2. Controlling SMUs using QCS Program.

    The next approach is to set the SMU voltage or current as part of the execution a QCS Program. There are three placements for where the SMU operations can be specified to occur.

    • Before a program execution as Pre-Program Operations

    • After a program execution as Post-Program Operations

    • SW Sweep Program execution as Pre-Iterate Operations

    First, let’s see how to set up SetCurrent and SetVoltage operations that will be part of the Program object.

    The SetCurrent Class

    Used to set target current and ramping rate for SMU channels.

    import keysight.qcs as qcs
    smu_channel = qcs.Channels(range(1), "smu_channel", True)
    const_current = qcs.Scalar("const_current", value=0.25e-6, dtype=float)
    ramp_rate = qcs.Scalar("ramp_rate", value=0.01, dtype=float)
    current_operation = qcs.SetCurrent(
                smu_channel, const_current, ramp_rate, "current_op"
    )
    

    The above code is used to set the current on one SMU channel. The default value for ramping_rate is None, which lets the backend determine a ramping rate.

    The SetVoltage Class

    Used to set target voltage and ramping rate for SMU channels.

    import keysight.qcs as qcs
    smu_channels = qcs.Channels(range(2), "smu_channels", True)
    const_voltages = qcs.Array("const_voltages", value=[0.25, 0.26], dtype=float)
    ramp_rates = qcs.Array("ramp_rates", value=[1.0, 2.0], dtype=float)
    voltage_operation = qcs.SetVoltage(
                smu_channels, const_voltages, ramp_rates, "current_ops"
    )
    

    The above code is used to set the voltage in two SMU channels. The default value for ramping_rate is None, which lets the backend determine a ramping rate. If the expected time of the operation is more than one second, then the ramp rate will be determined by the backend.

    Now let’s look the three options for how these operations can be inserted into a program.

    1. The add_pre_program_operation method (Pre-Program Operations)

      Pre-Program Operations run before program execution. They will only execute one time per program.

      Pre-Program Operations for Current

      import keysight.qcs as qcs
      control_channels = Channels(0, "xy_pulse")
      smu_channel = qcs.Channels(0, "smu_channel", True)
      backend = qcs.HclBackend(channel_mapper=mapper)
      program = qcs.Program()
      gauss = qcs.RFWaveform(duration=80e-9, envelope=qcs.GaussianEnvelope(num_sigma=4), amplitude=1, rf_frequency=5.1e9)
      program.add_waveform(gauss, control_channels)
      target_pre_current = qcs.Scalar("target_pre_current", value=1e-7, dtype=float)
      ramp_rate = qcs.Scalar("ramp_rate", value=1e-6, dtype=float)
      set_pre_current = qcs.SetCurrent(
          smu_channel, target_pre_current, ramp_rate
      )
      program.add_pre_program_operation(set_pre_current)
      program.n_shots(1000)
      program = qcs.Executor(backend).execute(program)
      

      Pre-Program Operations for Voltage

      import keysight.qcs as qcs
      control_channels = Channels(0, "xy_pulse")
      smu_channel = qcs.Channels(0, "smu_channel", True)
      backend = qcs.HclBackend(channel_mapper=mapper)
      program = qcs.Program()
      gauss = qcs.RFWaveform(duration=80e-9, envelope=qcs.GaussianEnvelope(num_sigma=4), amplitude=1, rf_frequency=5.1e9)
      program.add_waveform(gauss, control_channels)
      target_pre_voltage = qcs.Scalar("target_pre_voltage", value=1.88, dtype=float)
      ramp_rate = qcs.Scalar("ramp_rate", value=2.0, dtype=float)
      set_pre_voltage = qcs.SetVoltage(
          smu_channel, target_pre_voltage, ramp_rate
      )
      program.add_pre_program_operation(set_pre_voltage)
      program.n_shots(1000)
      program = qcs.Executor(backend).execute(program)
      
    2. The add_post_program_operation method (Post-Program Operations)

      Post-Program Operations run after program execution.They will only execute one time per program.

      Post-Program Operations for Current

      import keysight.qcs as qcs
      control_channels = Channels(0, "xy_pulse")
      smu_channel = qcs.Channels(0, "smu_channel", True)
      backend = qcs.HclBackend(channel_mapper=mapper)
      program = qcs.Program()
      gauss = qcs.RFWaveform(duration=80e-9, envelope=qcs.GaussianEnvelope(num_sigma=4), amplitude=1, rf_frequency=5.1e9)
      program.add_waveform(gauss, control_channels)
      target_post_current = qcs.Scalar("target_post_current", value=-1e-7, dtype=float)
      ramp_rate = qcs.Scalar("ramp_rate", value=1e-6, dtype=float)
      set_post_current = qcs.SetCurrent(
          smu_channel, target_post_current, ramp_rate
      )
      program.n_shots(1000)
      program.add_post_program_operation(set_post_current)
      program = qcs.Executor(backend).execute(program)
      

      Post-Program Operations for Voltage

      import keysight.qcs as qcs
      control_channels = Channels(0, "xy_pulse")
      smu_channel = qcs.Channels(0, "smu_channel", True)
      backend = qcs.HclBackend(channel_mapper=mapper)
      program = qcs.Program()
      gauss = qcs.RFWaveform(duration=80e-9, envelope=qcs.GaussianEnvelope(num_sigma=4), amplitude=1, rf_frequency=5.1e9)
      program.add_waveform(gauss, control_channels)
      target_post_voltage = qcs.Scalar("target_post_voltage", value=-3.66, dtype=float)
      ramp_rate = qcs.Scalar("ramp_rate", value=5.0, dtype=float)
      set_post_voltage = qcs.SetVoltage(
          smu_channel, target_post_voltage, ramp_rate
      )
      program.n_shots(1000)
      program.add_post_program_operation(set_post_voltage)
      program = qcs.Executor(backend).execute(program)
      
    3. The sweep method (Pre-Iterate Operations)

      These SMU operations run before a software-time sweep iteration. They will execute once for each sweep point.

      Pre-Iterate Operations for Current

      import keysight.qcs as qcs
      control_channels = Channels(0, "xy_pulse")
      smu_channel = qcs.Channels(0, "smu_channel", True)
      backend = qcs.HclBackend(channel_mapper=mapper)
      program = qcs.Program()
      gauss = qcs.RFWaveform(duration=80e-9, envelope=qcs.GaussianEnvelope(num_sigma=4), amplitude=1, rf_frequency=5.1e9)
      program.add_waveform(gauss, control_channels)
      target_current = qcs.Scalar("target_current", dtype=float)
      current_sweep = qcs.Array("current_sweep", value=[0.8e-7,0.5e-7,0.2e-7], dtype=float)
      ramp_rate = qcs.Scalar("ramp_rate", value=1e-6, dtype=float)
      set_current = qcs.SetCurrent(
          smu_channel, target_current, ramp_rate
      )
      program.n_shots(1000)
      program.sweep(current_sweep, target_current, pre_iterate_operation_list=[set_current])
      program = qcs.Executor(backend).execute(program)
      # Note: Sweep shall be SW Sweep for Voltage / Current. No HW Sweep possible for SMU.
      

      Pre-Iterate Operations for Voltage

      import keysight.qcs as qcs
      control_channels = Channels(0, "xy_pulse")
      smu_channel = qcs.Channels(0, "smu_channel", True)
      backend = qcs.HclBackend(channel_mapper=mapper)
      program = qcs.Program()
      gauss = qcs.RFWaveform(duration=80e-9, envelope=qcs.GaussianEnvelope(num_sigma=4), amplitude=1, rf_frequency=5.1e9)
      program.add_waveform(gauss, control_channels)
      target_voltage = qcs.Scalar("target_voltage", dtype=float)
      voltage_sweep = qcs.Array("voltage_sweep", value=[0.88,0.77,0.66, 0.55, 0.44], dtype=float)
      ramp_rate = qcs.Scalar("ramp_rate", value=1.0, dtype=float)
      set_voltage = qcs.SetVoltage(
          smu_channel, target_voltage, ramp_rate
      )
      program.n_shots(1000)
      program.sweep(voltage_sweep, target_voltage, pre_iterate_operation_list=[set_voltage])
      program = qcs.Executor(backend).execute(program)
      # Note: Sweep shall be SW Sweep for Voltage / Current. No HW Sweep possible for SMU.