Pulser server - syue99/Lab_control GitHub Wiki
Introduction
Pulser is the heart of our experiment. It is an OpalKelly FPGA XEM-6010-LX45 and custom made DDS that control the TTL and DDS signals of our experiment. The hardware related info can be found here
For the contents below, we give credit to Dr. Pruttivarasin's github wiki
Running the server
There are multiple ways of running LabRAD server. The easiest is to run this python script itself. At the end of this script, you will see
if __name__ == "__main__":
from labrad import util
util.runServer( Pulser() )
So if you run this script in the command_line like python PATH-TO\servers\control_instruments\pulser_ok.py it will run this chunk of code.
Pulser Server
Once you have used the Pulser/DDS for some time, you might want to dig into the code behind it. This page will attempt to explain the python code such that's it's possible to apply any modification to the code.
The code is basically written by Mike Ramm when we were graduate students at UC Berkeley. I, on the other hand, am not an expert in python code. So there might be some mistake. If you find one, please contact me and I will correct it.
Pulser server structure
The full code is in the repository. So I will not repost it here. I will instead try to explain a few important part of code.
Header of LabRAD server
'''
### BEGIN NODE INFO
[info]
name = Pulser
version = 1.2
description =
instancename = Pulser
[startup]
cmdline = %PYTHON% %FILE%
timeout = 20
[shutdown]
message = 987654321
timeout = 20
### END NODE INFO
'''
This header section is to decide which name of the server to display on the LabRAD manager.
LabRAD server class
class Pulser(LabradServer, DDS, LineTrigger):
A LabRAD server should inherit from the LabradServer class. In our case, we also inherit from DDS class (which also inherits from LabradServer class). We do this because the actual pulser hardware is doing many things like TTL switching, PMT counting, DDS pulser generator. The class DDS contains all the methods that's related to DDS frequency/amplitude programming. There's also a package api what contains FPGA board programming which we imported in this server.
What is LineTrigger?
LineTrigger is a functionality of the Pulser to start a pulse sequence conditioned on the state of one of its TTL input. This is useful in many scenarios.
First example is synchronisation to the 50/60 Hz AC power line. In an experiment where there are many electronic equipment nearby, there is magnetic field noise generated from current running in the power line in the lab. And of course depending on the phase of the 50/60 Hz, you might get different value of magnetic field. So if we run an experiment (pulse sequence) always at the same phase of the 50/60 Hz noise, then we decouple this noise from other noise in the lab.
Second example is synchronisation between multiple clocks. When we want to measure a frequency ration between two clocks, it is better to probe both clocks at the same time to establish common noise rejection of the clock laser. So one experiment can trigger another experiment. In this way, the timing between multiple experiments is completely under control.
Hardware Configuration file
There are many settings that can be changed depending on the DDS system. For example, different Pulser at different labs might have different reference frequency. At RIKEN we use 2 GHz, but at UCLA it might be 1 GHz. It's convenient to have one universal file that contains all the relevant setting.
I would like to hi-light a few useful settings in the hardwareConfiguration.py file. Added by Fred: For our system, the file is in config folder.
self.boardfreqrange = args.get('boardfreqrange', (0.0, 2000.0))
This line contains the reference frequency of the DDS board. In this case, it's 2000 MHz. DDS channel acts as a frequency scaler. So if you change your reference frequency to, for example, 1.5 GHz, you have to change this line to (0.0, 1500.0).
self.boardamplrange = args.get('boardamplrange', (-48.0, 6.0))
This is the range of the amplitude output of the DDS board (in dBm). Note that each DDS board will not output exactly the same amplitude even if you tell them to do so. This is because electronic components are a bit different. But they are roughly the same. Note that this is not the soft-limit of your DDS but the actual full range of amplitude your DDS can output.
'sMOT_PROBE':channelConfiguration(1, False, False, False, False),
This is a configuration for output TTL channel. First string is the name of the channel sMOT_PROBE. Next number is the physics channel address. Next four booleans are ismanual, manualstate, manualinversion, autoinversion.
ismanual boolean sets if this channel will be a TTL channel that we control manually by default. This is for TTL channel that's not part of the pulse sequence.
manualstate boolean sets the default state of the TTL channel.
manualinversion boolean sets whether we apply a NOT gate to this channel or not.
autoinversion boolean sets whether we apply a NOT gate to this channel when it's configured to be auto.
'DDS_0':ddsConfiguration( 0, (0.0,800.0), (-48.0,6.0), 70.0, -48.0),
The syntax is (address, allowedfreqrange, allowedamplrange, frequency, amplitude), where frequency is the default frequency of the channel once the Pulser is powered on and amplitude is the default amplitude.
After changing anything in hardwareConfiguration.py you have to restart the Pulser server!!
Beside outputting fixed frequency RF signal, the main functionality of the Pulser is to generate a pulse sequence to do experiment in atomic physics. Usually, experiment in atomic physics consists of a sequence of some switching of laser light by modulation of RF signal driving the AOMs and/or controlling mechanical shutters. The sequence is repeated many times to build-up enough statistics in the data acquired from the experiment. So in this tutorial, we will see how we can program pulse sequences to the Pulser.
**Programming a pulse sequence **
Comment by Fred: This is especially important when running experiment scripts.
We show here a simple example that allows us to program a pulse sequence to the system and run it. I show here a python script that does exactly that. We will then go through line-by-line later.
from labrad.units import WithUnit
from treedict import TreeDict
import labrad
cxn = labrad.connect()
p = cxn.pulser
p.new_sequence()
p.add_ttl_pulse('ttl_0', WithUnit(0, 'ms'), WithUnit(100, 'ms'))
p.add_ttl_pulse('ttl_0', WithUnit(200, 'ms'), WithUnit(100, 'ms'))
p.program_sequence()
p.start_number(1)
p.wait_sequence_done()
p.stop_sequence()
First few lines are some standard packages importation. The first import line of code is
p.new_sequence()
which defines a new sequence of the pulser.
Next we add to the pulse sequence a few TTL timing. The syntax is "channel_name, start_time and duration of the pulse." So
p.add_ttl_pulse('ttl_0', WithUnit(0, 'ms'), WithUnit(100, 'ms'))
will add a 100 ms-long TTL pulse that starts at t = 0 ms for TTL channel named 'ttl_0'. The naming of the channel can be changed in the hardwareConfiguration.py file.
Once you have added as many TTL pulses as you want, we then "program" the pulse sequence to the FPGA. This is the step where actual data transfer happens between the computer and the FPGA. We do this by executing
p.program_sequence()
Once the pulse sequence data is transferred to the FPGA, we can tell it to repeat the sequence as many times as we want. In this case, we only want to run the sequence once. So the number in the argument is '1'.
p.start_number(1)
Some sequences will take quite some time to complete. During this time, it's best not to do anything to the Pulser system. So this command
p.wait_sequence_done()
will wait until the pulse sequence is done (including the number of times we asked it to run). Then the last step is to 'shut down' by executing
p.stop_sequence()
Adding DDS pulses to the pulse sequence
Using only TTL pulses in your experiment might not be as flexible enough, especially is your setup has a few acousto-optical modulators (AOMs). In this case, it makes more sense to just control the RF signal driving the AOMs directly.
To add DDS pulses into the pulse sequence, after you add the TTL pulses (but before the p.program_sequence() line), you have to define a list of DDS pulses like this:
DDS = [('DDS_0', start_time, duration, frequency, amplitude, phase, frequency_ramp_rate, amplitude_ramp_rate),
('DDS_1', start_time, duration, frequency, amplitude, phase, frequency_ramp_rate, amplitude_ramp_rate),
]
Each argument is pretty self-explanatory. Make sure that they all have appropriate units. So, for example,
start_time = WithUnit(0.1,'ms')
duration = WithUnit(100.0, 'ms')
frequency = WithUnit(120.1, 'MHz')
amplitude = WithUnit(-20.0, 'dBm')
phase = WithUnit(0.0, 'deg')
Now, for the frequency_ramp_rate and amplitude_ramp_rate arguments, the correct units are something like MHz/s or dBm/s. But due to some difficulty in implement these unit, we instead use units per ms instead. So
frequency_ramp_rate = WithUnit(0.3, 'MHz')
will do a frequency ramp of the rate of 0.3 MHz per 1 millisecond. And
amplitude_ramp_rate = WithUnit(1.0, 'dBm')
will ramp the amplitude (linear in dB scale) at the rate of 1.0 dBm per 1 millisecond.
One import thing to remember is that you have to make sure yourself that you finish ramping within the duration of your DDS pulse. We will look into more details about frequency and amplitude ramping later.
Once you have defined the list of the DDS pulse in the variable DDS, then we have to program to the pulse by doing
p.add_dds_pulses(DDS)
and then we can do the normal p.program_sequence() as usual.
Others
Plot Sequence / Human Readout
Added by Fred: We add this feature when running an experiment. This basically translate the DDS and TTL pulses into graphs. Details can be find in the run an experiment part.
DDS (phase coherent dds board)
Phase Coherence
The phase coherent DDS boards maintain phase coherence of the generated RF signal if the generated frequency is kept constant. For example, if the pulse sequence has two back-to-back pulses of the same frequency, they will have the same phase as measured relative to an external clock. Any change of frequency destroys phase coherence. As long as the frequency is kept constant, however, the phase can be set to an arbitrary value for every pulse.
Immediate Switching
Switching the frequency on the DDS board takes several microseconds but changing the amplitude is immediate. Therefore to achieve switch to the new pulse conditions with no delays, the frequency has to be programmed first, and then the amplitude should be changed at the start time of the pulse. A 6 microsecond difference between frequency and amplitude programming should be sufficient.
Hardware Limits
-
Line triggering offset is limited to (0,15000) \mus. Due to a discrepancy between the real clock and the pulser clock, the maximum is slightly less than 1 / 60 Hz.
-
The minimum duration of a pulse is 160ns, which is 4 pulser clock cycles. The minimum duration of a gap between pulses is the same. Beyond the minimum, the resolution can be set in multiples of 40ns.
Version Tracking
Version 1.20
- new bit file pulser_2013_06_05.bit which fixes bug of not always reprogramming the pulser with a new sequence
- removed DAC settings
- extra debugging settings
Version 1.17
- Updated test file to match the latest pulse sequence inheritance
- Plotting works for local coherent channels
Version 1.16
- Changed the limits for line triggering offset to (0,15000) \mus. Due to a discrepancy between the real clock and the pulser clock, the maximum is slightly less than 1 / 60 Hz.
Version 1.15
- DDS Hardware settings distinguish between remote dds boards and phase coherent dds boards. This allows for phase coherent boards that are local. See parameters
phase_coherent_modelandremoteinHardwareConfiguration.py.sample.
Version 1.1
- Ability to do Line Triggering
Version 1.0.3
- Simplified clearing pmt counts when either the count rate or the mode is changed. Now, after every change first two counts from the FPGA are removed. This removed all spikes.
Version 1.0.2
- Bug fix: InCommunication DeferredLock was not used while using the 'output' setting. This led to freezes while rapidly turning pulser off and on.
Version 1.0.1
- Added resetstepDuration parameter to the HardwareConfiguration. This controls the duration of the TTL pulses used to advance and reset the DDS boards. Should be set to 2.
Version 1.0
-
Adds the ability to have an optical 2nd PMT, and DAC. (Merge from Dylan, CCT).
-
Fixed bug of not programming remaining pulses if a 0-length dds pulse encountered.
-
Syntax changed required for simpifying GUIs:
self.pulser.output(name, False)
instead of
self.pulser.select_channel(name)
self.pulser.output(False)
- DDS Lock Behavior Change:
The dds lock is activated when any pulse sequence is programmed. It can now be cleared only when the pulse sequence is completed or a a new setting called clear_dds_lock is called. This setting has to be called at the end of each experiment that requires to pulser even if the measurement is interrupted. This means one needs a try, except block and run the experiment in a console where it is sensitive to keyboard interrupt.