Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Active reset nqubits #546

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/runcards/galadriel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gates_settings:
reset_method: passive
passive_reset_duration: 100
minimum_clock_time: 4
active_reset: True
operations: []
gates:
M(0):
Expand Down
2 changes: 2 additions & 0 deletions src/qililab/experiment/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def build_execution(self):
# Translate circuits into pulses if needed
if self.circuits:
translator = CircuitToPulses(platform=self.platform)
if self.platform.gates_settings.active_reset is True:
self.circuits = [self.platform._add_active_reset_circuit(c) for c in self.circuits]
self.pulse_schedules = translator.translate(circuits=self.circuits)
# Build ``ExecutionManager`` class
self.execution_manager = EXECUTION_BUILDER.build(platform=self.platform, pulse_schedules=self.pulse_schedules)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class QbloxClusterController(QbloxController):
number_available_modules = 20
device: Cluster

active_reset: bool = False
active_reset_settings: list[dict[str, any]]
trigger_network_delay: int = 260

@dataclass
class QbloxClusterControllerSettings(QbloxController.QbloxControllerSettings):
"""Contains the settings of a specific Qblox Cluster Controller."""
Expand Down
166 changes: 163 additions & 3 deletions src/qililab/instruments/qblox/qblox_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,20 @@
from qpysequence import Waveforms, Weights
from qpysequence.library import long_wait
from qpysequence.program import Block, Loop, Register
from qpysequence.program.instructions import Play, ResetPh, SetAwgGain, SetPh, Stop
from qpysequence.program.instructions import (
Acquire,
LatchRst,
Play,
ResetPh,
SetAwgGain,
SetCond,
SetLatchEn,
SetPh,
Stop,
UpdParam,
Wait,
WaitSync,
)
from qpysequence.utils.constants import AWG_MAX_GAIN

from qililab.config import logger
Expand Down Expand Up @@ -56,6 +69,7 @@ class QbloxModuleSettings(AWG.AWGSettings):
Args:
awg_sequencers (Sequence[AWGQbloxSequencer]): list of settings for each sequencer
out_offsets (list[float]): list of offsets for each output of the qblox module
trigger network delay (int): delay between triggers sent to cluster's trigger network, see https://qblox-qblox-instruments.readthedocs-hosted.com/en/master/cluster/feedback.html
"""

awg_sequencers: Sequence[AWGQbloxSequencer]
Expand Down Expand Up @@ -212,18 +226,32 @@ def _generate_program( # pylint: disable=too-many-locals
"""

# Define program's blocks
program = Program()
# if active reset is enabled the wait sync instruction is added at the active reset block
program = Program(wait_sync=True) if self.settings.active_reset is False else Program(wait_sync=False)
# Create registers with 0 and 1 (necessary for qblox)
weight_registers = Register(), Register()
self._init_weights_registers(registers=weight_registers, values=(0, 1), program=program)
avg_loop = Loop(name="average", begin=int(self.nshots)) # type: ignore
bin_loop = Loop(name="bin", begin=0, end=self.num_bins, step=1)
avg_loop.append_component(bin_loop)
if self.settings.active_reset is True:
act_rst = Block(name="active_reset")
bin_loop.append_component(act_rst)
self._add_active_reset(
act_rst=act_rst,
bin_loop=bin_loop,
pulse_bus_schedule=pulse_bus_schedule,
waveforms=waveforms,
sequencer=sequencer,
weight_registers=weight_registers,
)

program.append_block(avg_loop)
stop = Block(name="stop")
stop.append_component(Stop())
program.append_block(block=stop)
timeline = pulse_bus_schedule.timeline

if len(timeline) > 0 and timeline[0].start_time != 0:
bin_loop.append_component(long_wait(wait_time=int(timeline[0].start_time)))

Expand All @@ -250,9 +278,122 @@ def _generate_program( # pylint: disable=too-many-locals
if wait_time > self._MIN_WAIT_TIME:
bin_loop.append_component(long_wait(wait_time=wait_time))

program.allocate_registers() # TODO: why does this fix registers
logger.info("Q1ASM program: \n %s", repr(program)) # pylint: disable=protected-access
return program

def _add_active_reset(
self,
act_rst: Block,
bin_loop: Loop,
pulse_bus_schedule: PulseBusSchedule,
waveforms: Waveforms,
sequencer: int,
weight_registers: tuple[Register, Register],
) -> Block:
"""Adds active reset sequence and removes active reset PulseEvents from pulse_bus_schedule

Args:
act_rst (Block): _description_
pulse_bus_schedule (PulseBusSchedule | None, optional): _description_. Defaults to None.

Returns:
Block: _description_
"""

# get pulse information from settings
act_rst_settings = self.settings.active_reset_settings
act_rst_wait = sum((setting["added"] for setting in act_rst_settings)) * self.settings.trigger_network_delay
setting_k = next(
enumerate(setting for setting in act_rst_settings if setting["qubit"] == pulse_bus_schedule.qubit)
)
pi_pulse = setting_k[1]["X"]
m_pulse = setting_k[1]["M"]
# mark added as true
self.settings.active_reset_settings[setting_k[0]]["added"] = True

# sync sequencers before start of sequence
act_rst.append_component(WaitSync(4))

if pulse_bus_schedule.port == "feedline_input":
# acquire instruction
# measure and add wait sync at the end

pulse_event = pi_pulse
waveform_pair = waveforms.find_pair_by_name(pulse_event.pulse.label()) # TODO: add pulse to waveforms

act_rst.append_component(ResetPh())
gain = int(np.abs(pulse_event.pulse.amplitude) * AWG_MAX_GAIN) # np.abs() needed for negative pulses
act_rst.append_component(SetAwgGain(gain_0=gain, gain_1=gain))
phase = int((pulse_event.pulse.phase % (2 * np.pi)) * 1e9 / (2 * np.pi))
act_rst.append_component(SetPh(phase=phase))

act_rst.append_component(
Play(
waveform_0=waveform_pair.waveform_i.index,
waveform_1=waveform_pair.waveform_q.index,
wait_time=4,
)
)
self._append_acquire_instruction(
loop=act_rst,
bin_index=bin_loop.counter_register,
sequencer_id=sequencer,
weight_regs=weight_registers,
acq_index=1,
)

pulse_bus_schedule.timeline = pulse_bus_schedule.timeline[1:] # erase event from timeline
# rst_pulse_time = pulse_event.duration #FIXME: this doesnt work because we are only substracting the M pulse duration and the X for drive (below). We should substract the whole rst duration
# for pulse_event in pulse_bus_schedule.timeline:
# pulse_event.start_time = pulse_event.start_time - rst_pulse_time

elif "drive" in pulse_bus_schedule.port:
pulse_event = pulse_bus_schedule.timeline[0]
waveform_pair = waveforms.find_pair_by_name(pulse_event.pulse.label())

# Conditional X pulses for active reset run after measurement
# active reset sequence 1
act_rst.append_component(SetLatchEn(1, 4)) # latch any trigger
# Reset the trigger network address counters, then wait on trigger address
act_rst.append_component(LatchRst(300)) # FIXME: 300 is to account for time of flight
# wait total of M pulse lenght + tof + integration length
integration_length = 2000 # TODO: hardcoded integration length
wait_time = pulse_event.start_time
act_rst.append_component(
long_wait(wait_time + integration_length)
) # wait for duration of measurement pulse + integration length

# trigger address conditional is 2^sequencer
act_rst.append_component(SetCond(1, 1, 0, pulse_event.duration))

act_rst.append_component(ResetPh())
gain = int(np.abs(pulse_event.pulse.amplitude) * AWG_MAX_GAIN) # np.abs() needed for negative pulses
act_rst.append_component(SetAwgGain(gain_0=gain, gain_1=gain))
phase = int((pulse_event.pulse.phase % (2 * np.pi)) * 1e9 / (2 * np.pi))
act_rst.append_component(SetPh(phase=phase))
act_rst.append_component(
Play(
waveform_0=waveform_pair.waveform_i.index,
waveform_1=waveform_pair.waveform_q.index,
wait_time=4, # we don't care about this wait time since we're running sync after it
)
)
act_rst.append_component(SetCond(0, 1, 0, 4))

pulse_bus_schedule.timeline = pulse_bus_schedule.timeline[1:] # erase event from timeline
# rst_pulse_time = pulse_event.duration
# for pulse_event in pulse_bus_schedule.timeline:
# pulse_event.start_time = pulse_event.start_time - rst_pulse_time

else: # compensate wait syncs for flux buses
act_rst.append_component(WaitSync(4))

# sync instruments at the end of sequence. #FIXME: this is just for safety, should eventually be removed
act_rst.append_component(
WaitSync(4)
) # TODO: we dont need this wait sync if times are calculated properly (and possibly don't need the one right above either)

def _init_weights_registers(self, registers: tuple[Register, Register], values: tuple[int, int], program: Program):
"""Initialize the weights `registers` to the `values` specified and place the required instructions in the
setup block of the `program`."""
Expand All @@ -267,6 +408,7 @@ def _generate_acquisitions(self) -> Acquisitions:
# FIXME: is it really necessary to generate acquisitions for a QCM??
acquisitions = Acquisitions()
acquisitions.add(name="default", num_bins=self.num_bins, index=0)
acquisitions.add(name="reset", num_bins=self.num_bins, index=1)
return acquisitions

@abstractmethod
Expand All @@ -279,10 +421,19 @@ def _generate_weights(self, sequencer: AWGQbloxSequencer) -> Weights:

@abstractmethod
def _append_acquire_instruction(
self, loop: Loop, bin_index: Register | int, sequencer_id: int, weight_regs: tuple[Register, Register]
self,
loop: Loop,
bin_index: Register | int,
sequencer_id: int,
weight_regs: tuple[Register, Register],
acq_index: int = 0,
):
"""Append an acquire instruction to the loop."""

@abstractmethod
def _set_active_reset_triggers(self, sequencer):
"""Enables triggers for all QRM sequencers. Used for active reset"""

def start_sequencer(self, port: str):
"""Start sequencer and execute the uploaded instructions."""
sequencers = self.get_sequencers_from_chip_port_id(chip_port_id=port)
Expand Down Expand Up @@ -523,6 +674,15 @@ def upload(self, port: str):
if (seq_idx := sequencer.identifier) in self.sequences:
sequence, uploaded = self.sequences[seq_idx]
self.device.sequencers[seq_idx].sync_en(True)

# TODO: temporal test
try:
self.device.sequencers[seq_idx].thresholded_acq_trigger_en(True)
self.device.sequencers[seq_idx].thresholded_acq_trigger_address(1)
except AttributeError as e:
# print(e)
pass

if not uploaded:
logger.info("Sequence program: \n %s", repr(sequence._program)) # pylint: disable=protected-access
self.device.sequencers[seq_idx].sequence(sequence.todict())
Expand Down
10 changes: 9 additions & 1 deletion src/qililab/instruments/qblox/qblox_qcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ def _generate_weights(self, sequencer: AWGQbloxSequencer) -> Weights:
return Weights()

def _append_acquire_instruction(
self, loop: Loop, bin_index: Register | int, sequencer_id: int, weight_regs: tuple[Register, Register]
self,
loop: Loop,
bin_index: Register | int,
sequencer_id: int,
weight_regs: tuple[Register, Register],
acq_index: int = 0,
):
"""Append an acquire instruction to the loop."""

Expand All @@ -61,3 +66,6 @@ def acquire_result(self) -> QbloxResult:
QbloxResult: Acquired Qblox result
"""
raise NotImplementedError

def _set_active_reset_triggers(self, sequencer):
"""Enables triggers for all QRM sequencers. Used for active reset"""
28 changes: 21 additions & 7 deletions src/qililab/instruments/qblox/qblox_qrm.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ def _set_device_integration_length(self, value: int, sequencer_id: int):
"""
self.device.sequencers[sequencer_id].integration_length_acq(value)

def _set_active_reset_triggers(self, sequencer):
"""Enables triggers for all QRM sequencers. Used for active reset"""
for sequencer in self.device.sequencers:
sequencer.thresholded_acq_trigger_en(True)
sequencer.thresholded_acq_trigger_address(sequencer._seq_idx + 1)
sequencer.thresholded_acq_trigger_invert(False)

def _set_device_scope_hardware_averaging(self, value: bool, sequencer_id: int):
"""set scope_hardware_averaging for the specific channel

Expand All @@ -235,11 +242,12 @@ def _set_device_threshold(self, value: float, sequencer_id: int):
"""Sets the threshold for classification at the specific channel.

Args:
value (float): Normalized threshold value (-1.0 to 1.0)
value (float): Normalized threshold value (-1.0 to 1.0) # FIXME: update docstring
sequencer_id (int): sequencer to update the value
"""
integer_value = int(value * self._get_sequencer_by_id(id=sequencer_id).used_integration_length)
self.device.sequencers[sequencer_id].thresholded_acq_threshold(integer_value)
integrated_value = value * self._get_sequencer_by_id(id=sequencer_id).used_integration_length
# min value -16777211
self.device.sequencers[sequencer_id].thresholded_acq_threshold(integrated_value)

def _set_device_threshold_rotation(self, value: float, sequencer_id: int):
"""Sets the threshold rotation for classification at the specific channel.
Expand Down Expand Up @@ -290,21 +298,27 @@ def get_acquisitions(self) -> QbloxResult:
return QbloxResult(integration_lengths=integration_lengths, qblox_raw_results=results)

def _append_acquire_instruction(
self, loop: Loop, bin_index: Register | int, sequencer_id: int, weight_regs: tuple[Register, Register]
self,
loop: Loop,
bin_index: Register | int,
sequencer_id: int,
weight_regs: tuple[Register, Register],
acq_index: int = 0,
):
"""Append an acquire instruction to the loop."""
weighed_acq = self._get_sequencer_by_id(id=sequencer_id).weighed_acq_enabled
integration_length = self.integration_length(sequencer_id=sequencer_id)

acq_instruction = (
AcquireWeighed(
acq_index=0,
acq_index=acq_index,
bin_index=bin_index,
weight_index_0=weight_regs[0],
weight_index_1=weight_regs[1],
wait_time=self._MIN_WAIT_TIME,
wait_time=integration_length,
)
if weighed_acq
else Acquire(acq_index=0, bin_index=bin_index, wait_time=self._MIN_WAIT_TIME)
else Acquire(acq_index=acq_index, bin_index=bin_index, wait_time=integration_length)
)
loop.append_component(acq_instruction)

Expand Down
Loading