{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 01 - Basic Sequencing with tProc v2\n", "\n", "**Objective:** Learn how to define pulses, create a sequence, acquire data, and understand the basic structure of a tProc v2 program." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Setup" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# Standard imports\n", "%matplotlib inline\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import logging\n", "\n", "from qick import *\n", "from qick.asm_v2 import AveragerProgramV2\n", "\n", "# Setup logging\n", "logging.basicConfig(level=logging.INFO, format='%(levelname)-8s [%(filename)s:%(lineno)d] %(message)s')\n", "logging.getLogger(\"qick_processor\").setLevel(logging.WARNING)\n", "\n", "# Connect to the board (adjust the path to your firmware)\n", "BITSTREAM_PATH = 'qick_216.bit'#'/path/to/your/firmware.bit' # <--- CHANGE THIS\n", "soc = QickSoc(BITSTREAM_PATH)\n", "soccfg = soc\n", "\n", "# Define hardware channels (adjust based on your firmware)\n", "GEN_CH = 0 # Generator channel 0\n", "RO_CH = 0 # Readout channel 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Your First Program: The Structure\n", "\n", "Every tProc v2 program follows this pattern:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "class MyFirstProgram(AveragerProgramV2):\n", " def _initialize(self, cfg):\n", " \"\"\"Setup: Called once when the program is compiled.\"\"\"\n", " # 1. Declare hardware resources\n", " # 2. Define envelopes and pulses\n", " # 3. Configure readout\n", " pass\n", "\n", " def _body(self, cfg):\n", " \"\"\"Sequence: Called for each repetition (shot).\"\"\"\n", " # 1. Play pulses\n", " # 2. Trigger readout\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The key distinction:\n", "- **`_initialize()`** runs during **compilation** (Python side, once)\n", "- **`_body()`** runs during **execution** (tProc side, many times)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. A Simple Single-Pulse Program\n", "\n", "Let's create a program that plays a single constant pulse and measures it." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7500b33b7f1a4e3bb9f80849a5907a33", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/10 [00:00" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "class SinglePulseProgram(AveragerProgramV2):\n", " def _initialize(self, cfg):\n", " # Declare the generator and readout channels\n", " self.declare_gen(ch=cfg['gen_ch'], nqz=1) # nqz=1 means no frequency folding\n", " self.declare_readout(ch=cfg['ro_ch'], length=cfg['ro_len'])\n", "\n", " # Define a constant pulse (no envelope shaping)\n", " self.add_pulse(\n", " ch=cfg['gen_ch'],\n", " name=\"my_pulse\",\n", " style=\"const\", # Constant amplitude\n", " freq=cfg['freq'], # Frequency in MHz\n", " ro_ch=cfg['ro_ch'], # Round freq to the readout's grid too (frequency matching)\n", " length=cfg['pulse_len'], # Duration in microseconds\n", " phase=cfg['phase'], # Phase in degrees\n", " gain=cfg['gain'] # Amplitude (0 to 1)\n", " )\n", "\n", " # Configure the readout\n", " self.add_readoutconfig(\n", " ch=cfg['ro_ch'],\n", " name=\"my_ro\",\n", " freq=cfg['freq'],\n", " gen_ch=cfg['gen_ch']\n", " )\n", " self.send_readoutconfig(ch=cfg['ro_ch'], name=\"my_ro\", t=0)\n", "\n", " def _body(self, cfg):\n", " # Play the pulse at time t=0\n", " self.pulse(ch=cfg['gen_ch'], name=\"my_pulse\", t=0)\n", " # Trigger the readout\n", " self.trigger(ros=[cfg['ro_ch']], pins=[0], t=cfg['trig_time'])\n", "\n", "# Configure the experiment\n", "config = {\n", " 'gen_ch': GEN_CH,\n", " 'ro_ch': RO_CH,\n", " 'freq': 500.0, # MHz\n", " 'pulse_len': 0.1, # microseconds\n", " 'phase': 0, # degrees\n", " 'gain': 1.0, # full amplitude\n", " 'trig_time': 0.2, # microseconds (when to start readout)\n", " 'ro_len': 0.5 # microseconds (readout duration)\n", "}\n", "\n", "# Create and run the program\n", "prog = SinglePulseProgram(soccfg, reps=1, final_delay=0.5, cfg=config)\n", "\n", "# Acquire data (decimated mode gives IQ traces vs time)\n", "iq_data = prog.acquire_decimated(soc, rounds=10) # 10 averages\n", "time_axis = prog.get_time_axis(ro_index=0)\n", "\n", "# Plot the results\n", "plt.figure(figsize=(10, 5))\n", "plt.plot(time_axis, iq_data[0][:,0], label='I (In-phase)')\n", "plt.plot(time_axis, iq_data[0][:,1], label='Q (Quadrature)')\n", "plt.plot(time_axis, np.abs(iq_data[0].dot([1,1j])), label='Magnitude')\n", "plt.legend()\n", "plt.xlabel('Time (µs)')\n", "plt.ylabel('Amplitude (a.u.)')\n", "plt.title('Single Pulse Measurement')\n", "plt.grid(True)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Explanation of the cell:**\n", "- `declare_gen()` and `declare_readout()` tell the tProc which hardware resources we'll use\n", "- `add_pulse()` defines a pulse with its parameters. A constant pulse (`style=\"const\"`) has flat amplitude.\n", "- `ro_ch` in `add_pulse()` and `gen_ch` in `add_readoutconfig()` **frequency-match** the generator and the readout, so both use exactly the same frequency. Without them the phase of the measured I/Q slides over time and changes from run to run; see [How to ensure frequency matching](../topics/freq_matching.rst).\n", "- In `_body()`, `pulse()` schedules the pulse, and `trigger()` starts the readout acquisition\n", "- `acquire_decimated()` returns averaged IQ data. The `rounds` parameter controls how many times the entire experiment is repeated for averaging." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Multiple Pulses in One Sequence\n", "\n", "You can define many pulses in `_initialize()` and play them in any order." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "1f9b1e43fcca4d2681774650c8851060", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/10 [00:00" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "class MultiPulseProgram(AveragerProgramV2):\n", " def _initialize(self, cfg):\n", " self.declare_gen(ch=cfg['gen_ch'], nqz=1)\n", " self.declare_readout(ch=cfg['ro_ch'], length=cfg['ro_len'])\n", "\n", " # Define a Gaussian envelope\n", " ramp_len = 0.2\n", " self.add_gauss(\n", " ch=cfg['gen_ch'],\n", " name=\"gauss_env\",\n", " sigma=ramp_len/10,\n", " length=ramp_len,\n", " even_length=True\n", " )\n", "\n", " # Define different pulse types\n", " # Flat-top pulse (Gaussian rise/fall, constant middle)\n", " self.add_pulse(\n", " ch=cfg['gen_ch'], name=\"p_flattop\",\n", " style=\"flat_top\", envelope=\"gauss_env\",\n", " freq=cfg['freq'], ro_ch=cfg['ro_ch'], length=0.1, phase=0, gain=1.0\n", " )\n", "\n", " # Gaussian pulse\n", " self.add_pulse(\n", " ch=cfg['gen_ch'], name=\"p_gauss\",\n", " style=\"arb\", envelope=\"gauss_env\",\n", " freq=cfg['freq'], ro_ch=cfg['ro_ch'], phase=0, gain=1.0\n", " )\n", "\n", " # Constant pulse\n", " self.add_pulse(\n", " ch=cfg['gen_ch'], name=\"p_const\",\n", " style=\"const\",\n", " freq=cfg['freq'], ro_ch=cfg['ro_ch'], length=0.15, phase=0, gain=1.0\n", " )\n", "\n", " # Flat-top pulse with 90-degree phase\n", " self.add_pulse(\n", " ch=cfg['gen_ch'], name=\"p_flattop_90\",\n", " style=\"flat_top\", envelope=\"gauss_env\",\n", " freq=cfg['freq'], ro_ch=cfg['ro_ch'], length=0.1, phase=90, gain=1.0\n", " )\n", "\n", " self.add_readoutconfig(ch=cfg['ro_ch'], name=\"my_ro\", freq=cfg['freq'], gen_ch=cfg['gen_ch'])\n", " self.send_readoutconfig(ch=cfg['ro_ch'], name=\"my_ro\", t=0)\n", "\n", " def _body(self, cfg):\n", " self.trigger(ros=[cfg['ro_ch']], pins=[0], t=cfg['trig_time'], ddr4=True)\n", "\n", " # Sequence of pulses at different times (microseconds)\n", " self.pulse(ch=cfg['gen_ch'], name=\"p_flattop\", t=0)\n", " self.pulse(ch=cfg['gen_ch'], name=\"p_gauss\", t=0.4)\n", " self.pulse(ch=cfg['gen_ch'], name=\"p_const\", t=0.8)\n", " self.pulse(ch=cfg['gen_ch'], name=\"p_flattop_90\", t=1.2)\n", " self.pulse(ch=cfg['gen_ch'], name=\"p_gauss\", t=1.6)\n", "\n", "# Run the multi-pulse sequence\n", "config['ro_len'] = 2.0 # Longer readout to capture all pulses\n", "prog = MultiPulseProgram(soccfg, reps=1, final_delay=0.5, cfg=config)\n", "iq_data = prog.acquire_decimated(soc, rounds=10)\n", "time_axis = prog.get_time_axis(ro_index=0)\n", "\n", "plt.figure(figsize=(12, 6))\n", "plt.plot(time_axis, iq_data[0][:,0], label='I')\n", "plt.plot(time_axis, iq_data[0][:,1], label='Q')\n", "plt.plot(time_axis, np.abs(iq_data[0].dot([1,1j])), label='Magnitude')\n", "plt.legend()\n", "plt.xlabel('Time (µs)')\n", "plt.ylabel('Amplitude (a.u.)')\n", "plt.title('Multi-Pulse Sequence')\n", "plt.grid(True)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Key observations from this example:**\n", "- Once a pulse is defined, it can be reused multiple times (`p_gauss` appears twice)\n", "- Different pulse styles serve different purposes: `const` for square pulses, `arb` for shaped pulses, `flat_top` for shaped pulses with a constant top\n", "- The `even_length=True` parameter ensures the envelope has an even number of samples, which is required for some operations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Inspecting Your Program\n", "\n", "You can print the program to see what the tProc will execute:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "macros:\n", "\tWriteReg(dst='s_core_w1', src=0)\n", "\tConfigReadout(ch=0, name='my_ro', t=0, tag=None, t_params={'t': }, t_regs={'t': 0})\n", "\tDelay(t=1.0, auto=True, gens=True, ros=True, tag=None, t_params={'t': }, t_regs={'t': 430})\n", "\tOpenLoop(n=1, name='reps')\n", "\tTrigger(ros=[0], tts=[], pins=[0], t=0.2, width=0.023251488095238096, ddr4=True, mr=False, tag=None, t_params={'t': , 'width': }, t_regs={'t': 86, 'width': 10}, outdict=defaultdict(, {}), trigset={0, 9, 10})\n", "\tPulse(ch=0, name='p_flattop', t=0, tag=None, t_params={'t': }, t_regs={'t': 0})\n", "\tPulse(ch=0, name='p_gauss', t=0.4, tag=None, t_params={'t': }, t_regs={'t': 172})\n", "\tPulse(ch=0, name='p_const', t=0.8, tag=None, t_params={'t': }, t_regs={'t': 344})\n", "\tPulse(ch=0, name='p_flattop_90', t=1.2, tag=None, t_params={'t': }, t_regs={'t': 516})\n", "\tPulse(ch=0, name='p_gauss', t=1.6, tag=None, t_params={'t': }, t_regs={'t': 688})\n", "\tWait(t=0, auto=True, gens=False, ros=True, tag=None, no_warn=True, t_params={'t': }, t_regs={'t': 946})\n", "\tDelay(t=0.5, auto=True, gens=True, ros=True, tag=None, t_params={'t': }, t_regs={'t': 1161})\n", "\tIncReg(dst='s_core_w1', src=1)\n", "\tCloseLoop()\n", "\tEnd()\n", "registers:\n", "\treps: QickRegisterV2(addr=0, init=None)\n", "pulses:\n", "\tp_flattop: QickPulse(waveforms=[Waveform(name='p_flattop_w0', freq=QickRawParam(par='freq', start=224054700, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=0, spans={}, quantize=1, steps={}), env=0, gain=QickRawParam(par='gain', start=32766, spans={}, quantize=2, steps={}), length=60, conf=8), Waveform(name='p_flattop_w1', freq=QickRawParam(par='freq', start=224054700, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=0, spans={}, quantize=1, steps={}), env=0, gain=QickRawParam(par='gain', start=16383, spans={}, quantize=1, steps={}), length=QickRawParam(par='length', start=60, spans={}, quantize=1, steps={}), conf=9), Waveform(name='p_flattop_w2', freq=QickRawParam(par='freq', start=224054700, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=0, spans={}, quantize=1, steps={}), env=60, gain=QickRawParam(par='gain', start=32766, spans={}, quantize=2, steps={}), length=60, conf=8)])\n", "\tp_gauss: QickPulse(waveforms=[Waveform(name='p_gauss_w0', freq=QickRawParam(par='freq', start=224054700, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=0, spans={}, quantize=1, steps={}), env=0, gain=QickRawParam(par='gain', start=32766, spans={}, quantize=1, steps={}), length=120, conf=8)])\n", "\tp_const: QickPulse(waveforms=[Waveform(name='p_const_w0', freq=QickRawParam(par='freq', start=224054700, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=0, spans={}, quantize=1, steps={}), env=0, gain=QickRawParam(par='gain', start=32766, spans={}, quantize=1, steps={}), length=QickRawParam(par='length', start=90, spans={}, quantize=1, steps={}), conf=9)])\n", "\tp_flattop_90: QickPulse(waveforms=[Waveform(name='p_flattop_90_w0', freq=QickRawParam(par='freq', start=224054700, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=1073741824, spans={}, quantize=1, steps={}), env=0, gain=QickRawParam(par='gain', start=32766, spans={}, quantize=2, steps={}), length=60, conf=8), Waveform(name='p_flattop_90_w1', freq=QickRawParam(par='freq', start=224054700, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=1073741824, spans={}, quantize=1, steps={}), env=0, gain=QickRawParam(par='gain', start=16383, spans={}, quantize=1, steps={}), length=QickRawParam(par='length', start=60, spans={}, quantize=1, steps={}), conf=9), Waveform(name='p_flattop_90_w2', freq=QickRawParam(par='freq', start=224054700, spans={}, quantize=10, steps={}), phase=QickRawParam(par='phase', start=1073741824, spans={}, quantize=1, steps={}), env=60, gain=QickRawParam(par='gain', start=32766, spans={}, quantize=2, steps={}), length=60, conf=8)])\n", "\tmy_ro: QickPulse(waveforms=[Waveform(name='my_ro_w0', freq=QickRawParam(par='freq', start=-873813330, spans={}, quantize=-39, steps={}), phase=0, env=0, gain=0, length=3, conf=264)])\n", "expanded ASM:\n", "\t NOP \n", "\t REG_WR s12 imm #0 \n", "\t WPORT_WR p4 wmem [&8] @0 \n", "\t TIME #430 inc_ref \n", "\t REG_WR r0 imm #0 \n", "\treps:\n", "\t TRIG p0 set @86 \n", "\t TRIG p9 set @86 \n", "\t TRIG p10 set @86 \n", "\t TRIG p0 clr @96 \n", "\t TRIG p9 clr @96 \n", "\t TRIG p10 clr @96 \n", "\t WPORT_WR p0 wmem [&0] @0 \n", "\t WPORT_WR p0 wmem [&1] @0 \n", "\t WPORT_WR p0 wmem [&2] @0 \n", "\t WPORT_WR p0 wmem [&3] @172 \n", "\t WPORT_WR p0 wmem [&4] @344 \n", "\t WPORT_WR p0 wmem [&5] @516 \n", "\t WPORT_WR p0 wmem [&6] @516 \n", "\t WPORT_WR p0 wmem [&7] @516 \n", "\t WPORT_WR p0 wmem [&3] @688 \n", "\t REG_WR s15 label SKIP \n", "\t WAIT [s15] @946 time \n", "\t TIME #1161 inc_ref \n", "\t REG_WR s12 op -op(s12 + #1) \n", "\t REG_WR s15 label reps \n", "\t TEST -op(r0 - #0) \n", "\t JUMP [s15] -if(NZ) -wr(r0 op) -op(r0 + #1) \n", "\t REG_WR s15 label NEXT \n", "\t JUMP [s15] \n" ] } ], "source": [ "print(prog)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Explanation:** This prints the compiled program in three sections:\n", "1. **macros:** High-level operations (pulse definitions, loops, triggers)\n", "2. **registers:** Allocated data registers\n", "3. **pulses:** Defined waveforms with their parameters\n", "4. **expanded ASM:** The actual assembly code that runs on the tProc\n", "\n", "Understanding this output becomes important when debugging complex sequences." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. Summary\n", "\n", "You have learned:\n", "- The structure of a tProc v2 program (`_initialize` vs `_body`)\n", "- How to define and play pulses of different styles (const, arb, flat_top)\n", "- How to trigger readout and acquire IQ data\n", "- How to inspect the generated assembly code\n", "\n", "**Next steps:** Proceed to [``02_Parameter_Sweeps.ipynb``](./02_Parameter_Sweeps.ipynb) to learn how to automate parameter variation and create 2D scans." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.4" } }, "nbformat": 4, "nbformat_minor": 4 }