Skip to content
Snippets Groups Projects
Commit cb87e445 authored by Sander Snoo's avatar Sander Snoo
Browse files

Removed old C++ Keysight uploader.

parent 67f42be7
No related branches found
No related tags found
No related merge requests found
Showing
with 3 additions and 14667 deletions
......@@ -3,10 +3,7 @@ import copy
from pulse_lib.segments.segment_container import segment_container
from pulse_lib.sequencer import sequencer
from pulse_lib.keysight.uploader import keysight_uploader
from pulse_lib.keysight.uploader_core.uploader import keysight_upload_module
from pulse_lib.virtual_channel_constructors import virtual_gates_constructor
from pulse_lib.keysight.M3202A_uploader import M3202A_Uploader
class pulselib:
......@@ -128,14 +125,8 @@ class pulselib:
# TODO rewrite, so this function is embedded in the other ones.
if self._backend == "keysight":
self.cpp_uploader = keysight_upload_module()
for name, awg in self.awg_devices.items():
if awg is not None:
self.cpp_uploader.add_awg_module(name, awg)
self.uploader = keysight_uploader(self.awg_devices, self.cpp_uploader, self.awg_channels,
self.channels_to_physical_locations , self.channel_delays_computed,
self.channel_compenstation_limits, self.AWG_to_dac_ratio)
raise Exception('Old keysight driver is not supported anymore. Use M3202A driver and backend="M3202A"')
elif self._backend == "M3202A":
self.uploader = M3202A_Uploader(self.awg_devices, self.awg_channels, self.channels_to_physical_locations,
self.channel_delays_computed, self.channel_compenstation_limits, self.AWG_to_dac_ratio)
......
import time
import logging
from pulse_lib.keysight.uploader_core.uploader import waveform_cache_container
class keysight_uploader():
"""
Object responsible for uploading waveforms to the keysight AWG in a timely fashion.
"""
def __init__(self, AWGs, cpp_uploader,channel_names, channel_locations, channel_delays, channel_compenstation_limits, AWG_to_dac_ratio):
'''
Initialize the keysight uploader.
Args:
AWGs (dict<awg_name,QcodesIntrument>) : list with AWG's
cpp_uploader (keysight_upload_module) : class that performs normalisation and conversion of the wavorm to short + upload.
channel_names(list) : list with all the names of the channels
channel_locations (dict): dict with channel and AWG+channel location
channel_compenstation_limits (dict) : dict with channel name as key and tuple as value with lower and upper limit
Returns:
None
'''
self.memory_allocation = dict()
# TODO reinit memory on start-up
self.AWGs = AWGs
self.current_HVI = None
self.current_HVI_ID = None
self.cpp_uploader = cpp_uploader
self.channel_names = channel_names
self.channel_map = channel_locations
self.channel_delays = channel_delays
self.channel_compenstation_limits = channel_compenstation_limits
self.AWG_to_dac_ratio = AWG_to_dac_ratio
self.upload_queue = []
self.upload_ready_to_start = []
self.upload_done = []
def create_job(self, sequence, index, seq_id, n_rep, prescaler=0, neutralize=True):
return upload_job(sequence, index, seq_id, n_rep, prescaler, neutralize)
def add_upload_job(self, job):
'''
add a job to the uploader.
Args:
job (upload_job) : upload_job object that defines what needs to be uploaded and possible post processing of the waveforms (if needed)
'''
self.upload(job)
def __get_upload_data(self, seq_id, index):
"""
get job data of an uploaded segment
Args:
seq_id (uuid) : id of the sequence
index (tuple) : index that has to be played
Return:
job (upload_job) :job, with locations of the sequences to be uploaded.
"""
# check if job if job is uploaded.
for j in range(5):
for i in range(len(self.upload_ready_to_start)):
job = self.upload_ready_to_start[i]
if job.id == seq_id and job.index == index:
return self.upload_ready_to_start.pop(i)
raise ValueError("Sequence with id {}, index {} not placed for upload .. . Always make sure to first upload your segment and then do the playback.")
def _segment_AWG_memory(self):
'''
Generates segments in the memory in the Keysight AWG.
'''
self.cpp_uploader.resegment_memory()
# set to single shot meaurements. This is the default option for HVI based code.
for channel, channel_loc in self.channel_map.items():
self.awg[channel_loc[0]].awg_queue_config(channel_loc[1], 0)
def play(self, seq_id, index, release = True):
"""
start playback of a sequence that has been uploaded.
Args:
seq_id (uuid) : id of the sequence
index (tuple) : index that has to be played
release (bool) : release memory on AWG after done.
"""
"""
steps :
0) get upload data (min max voltages for all the channels, total time of the sequence, location where things are stored in the AWG memory.) and wait until the AWG is idle
1) set voltages for all the channels.
2) make queue for each channels (now assuming single waveform upload).
3) upload HVI code & start.
"""
# 0)
job = self.__get_upload_data(seq_id, index)
self.wait_until_AWG_idle()
# 1 + 2)
# flush the queue's
for channel_name, data in job.upload_data.items():
"""
upload data <tuple>:
[0] <tuple <double>> : min output voltate, max output voltage
[1] <list <tuple <mem_loc<int>, n_rep<int>, precaler<int>> : upload locations of differnt segments
(by definition backend now merges all segments in 1 since it should
not slow you down, but option is left open if this would change .. )
"""
awg_name, channel_number = self.channel_map[channel_name.decode('ascii')]
v_pp, v_off = convert_min_max_to_vpp_voff(*data[0])
# This should happen in HVI
# self.AWGs[awg_name].awg_stop(channel_number)
self.AWGs[awg_name].set_channel_amplitude(v_pp/1000/2,channel_number) #amp = vpp/2 (speciefied in V on module, so therefore factor 1000)
self.AWGs[awg_name].set_channel_offset(v_off/1000,channel_number)
self.AWGs[awg_name].awg_flush(channel_number)
start_delay = 0 # no start delay
trigger_mode = 1 # software/HVI trigger
cycles = 1
prescaler = job.prescaler
for segment_number in data[1]:
self.AWGs[awg_name].awg_queue_waveform(channel_number,segment_number,trigger_mode,start_delay,cycles,prescaler)
trigger_mode = 0 # Auto tigger -- next waveform will play automatically.
# 3)
if job.HVI_start_function is None:
job.HVI.load()
job.HVI.start()
else:
job.HVI_start_function(job.HVI, self.AWGs, self.channel_map, job.playback_time, job.n_rep, **job.HVI_kwargs)
if release == True:
self._release_memory_jobs()
self.upload_done.append(job)
else:
# return job to queue for reuse of waveforms
self.upload_ready_to_start.append(job)
def release_memory(self, seq_id=None, index=None):
for job in self.upload_ready_to_start:
if (seq_id is None
or (job.seq_id == seq_id and (index is None or job.index == index))):
self.upload_done.append(job)
self._release_memory_jobs()
def _release_memory_jobs(self):
# release the memory of all jobs that are uploaded. Be careful to do not run this when active playback is happening. Otherwise you risk of overwriting a waveform while playing.
for job in self.upload_done:
self.cpp_uploader.release_memory(job.waveform_cache)
self.upload_done = []
def upload(self, job):
'''
Class taking care of putting the waveform on the right AWG. This is a continuous thread that is run in the background.
Steps:
1) get all the upload data
2) perform DC correction (if needed)
3) compile the HVI script for the next upload
5a) convert data in an aprropriate upload format (c++)
5b) upload all data (c++)
6) write in the job object the resulting locations of sequences that have been uploaded.
'''
start = time.perf_counter()
logging.debug('uploading')
# 1) get all the upload data -- construct object to hall the rendered data
waveform_cache = waveform_cache_container(self.channel_map, self.channel_compenstation_limits)
sample_rate = job.sample_rate
for i in range(len(job.sequence)):
seg = job.sequence[i]
# TODO add precaler in as sample rate
for channel in self.channel_names:
pre_delay = 0
post_delay = 0
if i == 0:
pre_delay = self.channel_delays[channel][0]
if i == len(job.sequence) -1:
post_delay = self.channel_delays[channel][1]
wvf = seg.get_waveform(channel, job.index, pre_delay, post_delay, sample_rate)
integral = 0
if job.neutralize == True:
integral = getattr(seg, channel).integrate(job.index, pre_delay, post_delay, sample_rate)
vmin = getattr(seg, channel).v_min(job.index, sample_rate)
vmax = getattr(seg, channel).v_max(job.index, sample_rate)
if channel in self.AWG_to_dac_ratio.keys(): #start Luca modification
ratio = self.AWG_to_dac_ratio[channel]
else:
ratio = 1 #end Luca modification
waveform_cache[channel].add_data(wvf/ratio, (vmin/ratio, vmax/ratio), integral/ratio)
pre_delay = 0
post_delay = 0
# 2) perform DC correction (if needed)
'''
Steps: [TODO : best way to include sample rate here? (by default now 1GS/s)]
a) calculate total compensation time needed (based on given boundaries).
b) make sure time is modulo 10 (do that here?)
c) add segments with the compenstated pulse for the given total time.
'''
waveform_cache.generate_DC_compenstation(sample_rate)
# TODO express this in time instead of points (now assumed one ns is point in the AWG (not very robust..))
job.waveform_cache = waveform_cache
if job.prescaler == 0:
job.playback_time = waveform_cache.npt
elif job.prescaler == 1:
job.playback_time = waveform_cache.npt*5*job.prescaler
else:
job.playback_time = waveform_cache.npt*5*job.prescaler*2
# 3)
if job.HVI is not None:
job.compile_HVI()
duration = time.perf_counter() - start
logging.info(f'generated wavefroms in {duration*1000:6.3f} ms')
start = time.perf_counter()
# 3 + 4a+b)
job.upload_data = self.cpp_uploader.add_upload_data(waveform_cache)
# submit the current job as completed.
self.upload_ready_to_start.append(job)
duration = time.perf_counter() - start
logging.info(f'uploaded in {duration*1000:6.3f} ms')
def wait_until_AWG_idle(self):
'''
check if the AWG is doing playback, when done, release this function
'''
# assume all awg's are used and also all the channels
awg_name, channel = next(iter(self.channel_map.values()))
awg = self.AWGs[awg_name]
while awg.awg.AWGisRunning(channel):
time.sleep(0.001)
class upload_job(object):
"""docstring for upload_job"""
def __init__(self, sequence, index, seq_id, n_rep, prescaler=0, neutralize=True, priority=0):
'''
Args:
sequence (list of segment_container): list with segment_containers in sequence
index (tuple) : index that needs to be uploaded
seq_id (uuid) : id of the sequence
n_rep (int) : number of repetitions of this sequence.
prescaler (int) : scale the upluading speeds (f_sampling = 1Gs/(5*prescaler))
neutralize (bool) : place a neutralizing segment at the end of the upload
priority (int) : priority of the job (the higher one will be excuted first)
'''
self.sequence = sequence
self.id = seq_id
self.index = index
self.n_rep = n_rep
self.prescaler = prescaler
self.sample_rate = convert_prescaler_to_sample_rate(prescaler)
self.neutralize = neutralize
self.priority = priority
self.playback_time = 0 #total playtime of the waveform
self.upload_data = None
self.waveform_cache = None
self.HVI = None
def add_HVI(self, HVI, compile_function, start_function, **kwargs):
"""
Introduce HVI functionality to the upload.
args:
HVI (SD_HVI) : HVI object from the keysight libraries
compile_function (function) : function that compiles the HVI code. Default arguments that will be provided are (HVI, npt, n_rep) = (HVI object, number of points of the sequence, number of repetitions wanted)
start_function (function) :function to be executed to start the HVI (this can also be None)
kwargs : keyword arguments for the HVI script (see usage in the examples (e.g. when you want to provide your digitzer card))
"""
self.HVI = HVI
self.HVI_compile_function = compile_function
self.HVI_start_function = start_function
self.HVI_kwargs = kwargs
def compile_HVI(self):
self.HVI_compile_function(self.HVI, self.playback_time, self.n_rep, **self.HVI_kwargs)
def convert_min_max_to_vpp_voff(v_min, v_max):
# vpp = v_max - v_min
# voff = (v_min + v_max)/2
voff = 0
vpp = max(abs(v_min), abs(v_max))*2
return vpp, voff
def convert_prescaler_to_sample_rate(prescalor):
"""
Keysight specific function.
Args:
prescalor (int) : prescalor set to the awg.
Returns:
sample_rate (float) : effective sample rate the AWG will be running
"""
if prescalor == 0:
return 1e9
if prescalor == 1:
return 200e6
else:
return 1e9/(2*5*prescalor)
\ No newline at end of file
/uploader.cpp
#include "keysight_awg_post_processing_and_upload.h"
#include <map>
#include <string>
#include <vector>
#include <stdexcept>
#include <iostream>
#include <math.h>
cpp_uploader::cpp_uploader(){}
cpp_uploader::~cpp_uploader(){}
void cpp_uploader::add_awg_module(std::string AWG_name, int chassis, int slot){
SD_Module *my_SD_module;
my_SD_module = new SD_Module(0);
SD_AIO *my_AWG_module;
my_AWG_module = new SD_AIO();
int error_handle;
char* ProductName = new char[128];
error_handle = my_SD_module->getProductName(chassis, slot, ProductName);
check_error(my_SD_module, &error_handle);
error_handle = my_AWG_module->open(ProductName, chassis, slot, 1);
check_error(my_SD_module, &error_handle);
delete[] ProductName;
AWG_modules[AWG_name] = my_AWG_module;
SD_modules[AWG_name] = my_SD_module;
mem_mgr[AWG_name] = new mem_ctrl();
error_handles[AWG_name] = error_handle;
}
void cpp_uploader::add_upload_job(std::map<std::string, std::map<int, waveform_raw_upload_data*>> *upload_data){
// make first time estimation to determine if multithreading useful (overhead ~ 5ms) (times in ms)
double time_no_multi_upload = *upload_data->begin()->second.begin()->second->npt * 4*upload_data->size()/10e3;
double time_multi_upload = *upload_data->begin()->second.begin()->second->npt*4/10e3 + 5;
#pragma omp parallel for if(time_multi_upload < time_no_multi_upload)
for (int i = 0; i < upload_data->size(); ++i){
auto AWG_iterator = upload_data->begin();
advance(AWG_iterator, i);
for (auto channel_iterator = AWG_iterator->second.begin(); channel_iterator != AWG_iterator->second.end(); ++channel_iterator){
rescale_concatenate_and_convert_to_16_bit_number(channel_iterator->second);
load_data_on_awg(AWG_iterator->first, channel_iterator->second);
free_cache(channel_iterator->second);
}
}
}
void cpp_uploader::rescale_concatenate_and_convert_to_16_bit_number(waveform_raw_upload_data* upload_data){
/*
low level function the voltages to 0/1 range and making a conversion to 16 bits.
All voltages are also concatenated
*/
upload_data->upload_data = new short[*upload_data->npt];
double *wvf_ptr;
double v_offset = 0;
double v_pp = fmax(fabs(upload_data->min_max_voltage->first),fabs(upload_data->min_max_voltage->second))*2;
// Alternative, define v_offset, but atm bad idea, since the AWG takes v_offset as default zero. -> Bias T problems.
// double v_offset = (upload_data->min_max_voltage->second + upload_data->min_max_voltage->first)/2;
// double v_pp = upload_data->min_max_voltage->second - upload_data->min_max_voltage->first;
double offset_factor = 0; // v_offset + v_pp*0.5;
double rescaling_factor = 65535./v_pp;
size_t i = 0;
for(size_t wvf_id = 0; wvf_id < upload_data->wvf_npt->size(); ++wvf_id ){
wvf_ptr = upload_data->wvf_data->at(wvf_id);
for(int idx_view = 0; idx_view < upload_data->wvf_npt->at(wvf_id); ++ idx_view){
upload_data->upload_data[i] = ( wvf_ptr[idx_view] - offset_factor)*rescaling_factor;
++i;
}
}
}
void cpp_uploader::load_data_on_awg(std::string awg_name, waveform_raw_upload_data* upload_data){
int segment_location = mem_mgr.find(awg_name)->second->get_upload_slot(*upload_data->npt).first;
if (segment_location == -1)
throw std::invalid_argument("No segments available on the AWG/segment is too long ..");
error_handles[awg_name] = AWG_modules[awg_name]->waveformReLoad(0, *upload_data->npt, upload_data->upload_data, segment_location, 0);
check_error(SD_modules[awg_name], &error_handles[awg_name]);
upload_data->data_location_on_AWG.push_back(segment_location);
}
void cpp_uploader::free_cache(waveform_raw_upload_data* upload_data){
delete[] upload_data->upload_data;
}
void cpp_uploader::release_memory(std::map<std::string, std::map<int, waveform_raw_upload_data*>>* upload_data){
// release memory in memory manager object
for (auto AWG_iterator = upload_data->begin(); AWG_iterator != upload_data->end(); ++AWG_iterator){
for (auto channel_iterator = AWG_iterator->second.begin(); channel_iterator != AWG_iterator->second.end(); ++channel_iterator){
mem_mgr.find(AWG_iterator->first)->second->release_memory(channel_iterator->second->data_location_on_AWG);
}
}
}
void cpp_uploader::check_error(SD_Module *AWG_module, int *error_handle){
if (*error_handle < 0){
std::cout << "error : \t" << *error_handle << "\t\t"<< (AWG_module->getErrorMessage(*error_handle)) << "\n";
//throw std::invalid_argument(AWG_module->getErrorMessage(*error_handle));
}
}
void cpp_uploader::resegment_memory(){
/*
apply the segment allocation on the AWG. As provided in the memctrl object.
*/
#pragma omp parallel for
for (int i = 0; i < AWG_modules.size(); ++i){
std::map<std::string, SD_AIO*>::iterator my_AWG_module_iter = AWG_modules.begin();
advance(my_AWG_module_iter, i);
// completely reinit the memory.
mem_ctrl * mem_ctrl_tmp = mem_mgr[my_AWG_module_iter->first];
delete mem_ctrl_tmp;
mem_mgr[my_AWG_module_iter->first] = new mem_ctrl();
std::vector<int> *waveformSize =mem_mgr[my_AWG_module_iter->first]->get_segment_occupation()->get_memory_sizes();
std::map<int, std::vector<int>> *seg_data = mem_mgr[my_AWG_module_iter->first]->get_segment_occupation()->get_seg_data();
error_handles[my_AWG_module_iter->first] = my_AWG_module_iter->second->waveformFlush();
check_error(SD_modules[my_AWG_module_iter->first], &error_handles[my_AWG_module_iter->first]);
for (int i = 0; i < waveformSize->size(); ++i)
{
short* waveformDataRaw = new short[waveformSize->at(i)];
for (int j = 0; j < seg_data->at(waveformSize->at(i)).size(); ++j)
{
// upload to the AWG
error_handles[my_AWG_module_iter->first] = my_AWG_module_iter->second->waveformLoad(0, waveformSize->at(i), waveformDataRaw, seg_data->at(waveformSize->at(i)).at(j), 0);
check_error(SD_modules[my_AWG_module_iter->first], &error_handles[my_AWG_module_iter->first]);
}
delete[] waveformDataRaw;
}
}
}
#ifdef linux
#include <Keysight/SD1/cpp/SD_Module.h>
#include <Keysight/SD1/cpp/SD_AIO.h>
#endif
#ifdef _WIN32
#include <Libraries/include/cpp/SD_Module.h>
#include <Libraries/include/cpp/SD_AIO.h>
#endif
#include "mem_ctrl.h"
#include <map>
#include <string>
#include <vector>
struct waveform_raw_upload_data{
std::vector<double*> *wvf_data;
std::vector<int> *wvf_npt;
std::pair<double, double> *min_max_voltage;
short *upload_data;
int *npt;
std::vector<int> data_location_on_AWG;
};
class cpp_uploader
{
std::map<std::string, SD_Module*> SD_modules;
std::map<std::string, SD_AIO*> AWG_modules;
std::map<std::string, mem_ctrl*> mem_mgr;
std::map<std::string, int> error_handles;
public:
cpp_uploader();
~cpp_uploader();
void add_awg_module(std::string AWG_name, int chassis, int slot);
void add_upload_job(std::map<std::string, std::map<int, waveform_raw_upload_data*>> *upload_data);
void release_memory(std::map<std::string, std::map<int, waveform_raw_upload_data*>>* upload_data);
void resegment_memory();
private:
void rescale_concatenate_and_convert_to_16_bit_number(waveform_raw_upload_data* upload_data);
void load_data_on_awg(std::string awg_name, waveform_raw_upload_data* upload_data);
void free_cache(waveform_raw_upload_data* upload_data);
void check_error(SD_Module *AWG_module, int *error_handle);
};
/*
Here classes can be found that control the memory occupation on each AWG module.
*/
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include "mem_ctrl.h"
std::vector<int> int_linspace(int start_in, int end_in){
std::vector<int> int_linspaced;
for (int i = start_in; i < end_in; ++i){
int_linspaced.push_back(i);
}
return int_linspaced;
}
segment_occupation::segment_occupation(std::map<int, int>* mem_layout){
/*
Object that manages the occupation of the memory of the AWG.
The Memory is pre-segmented on the AWG. E.g you have to say that you want to be able to upload 100 sequences with lenth L.
Currently this is a simple system that just assigns memory locations to certain segments.
There is are currently no advanced capabilities build in to reuse the memory in the AWG. This could be done later if performance would be limiting.
A compelling reason not to reuse segments would be in the assumption one would regularly use DSP which would make it hard to reuse things.
*/
memory_layout = mem_layout;
int mem_number = 0;
for (auto it = memory_layout->begin(); it != memory_layout->end(); ++it){
memory_sizes.push_back(it->first);
}
std::sort(memory_sizes.begin(), memory_sizes.end());
for (size_t i = 0; i < memory_sizes.size(); i++){
seg_data[memory_sizes[i]] = int_linspace(mem_number, mem_number + memory_layout->find(memory_sizes[i])->second);
mem_number += memory_layout->find(memory_sizes[i])->second;
index_info.push_back(mem_number);
}
}
std::pair<int, int> segment_occupation::request_new_segment(int size){
/*
Request a new segment in the memory with a certain size
Args:
size : size you want to reserve in the AWG memory
returns:
new_segment_info : segment_number and maxisize
*/
std::vector<int> valid_sizes;
std::pair<int,int> new_segment_info = std::make_pair(-1,0);
for (size_t i = 0; i < memory_sizes.size(); ++i){
if (size <= memory_sizes[i]){
valid_sizes.push_back(memory_sizes[i]);
}
}
std::vector<int> *mem_locations;
for (size_t i = 0; i < valid_sizes.size(); ++i)
{
mem_locations = &seg_data.find(valid_sizes[i])->second;
if (mem_locations->size() > 0){
new_segment_info.first = mem_locations->at(0);
new_segment_info.second = valid_sizes[i];
mem_locations->erase(mem_locations->begin());
break;
}
}
return new_segment_info;
}
void segment_occupation::free_segment(int seg_number){
/*
adds a segment as free back to the usable segment pool
Args:
seg_number number of the segment in the ram of the AWG
*/
int seg_loc = 0;
for (size_t i = 0; i < index_info.size(); ++i) {
if (seg_number < index_info[i]){
seg_loc = i;
break;
}
}
seg_data.find(memory_sizes[seg_loc])->second.push_back(seg_number);
}
std::vector<int> *segment_occupation::get_memory_sizes(){
return &memory_sizes;
}
std::map<int, std::vector<int>> *segment_occupation::get_seg_data(){
return &seg_data;
}
mem_ctrl::mem_ctrl(){
// Initialize memory management object.
memory_layout[1e8] = 4;
memory_layout[5e7] = 4;
memory_layout[1e7] = 4;
memory_layout[5e6] = 8;
memory_layout[1e6] = 10;
memory_layout[1e5] = 400;
memory_layout[1e4] = 500;
seg_occ = new segment_occupation(&memory_layout);
};
mem_ctrl::~mem_ctrl(){
delete seg_occ;
}
std::pair<int, int> mem_ctrl::get_upload_slot(int n_points){
/*
get a location where the data can be uploaded in the memory.
Args:
n_points (int) : number of points that will be saved in the memory
returns:
segment_number, segment_size : the segment number where the segment can be uploaded to in the memory of the AWG.
*/
return seg_occ->request_new_segment(n_points);
}
void mem_ctrl::release_memory(std::vector<int> segments_numbers){
/*
release memory when segments are not longer needed.
Args:
segments_numbers (array<int>) : list with segments number to be released
*/
for (size_t i = 0; i < segments_numbers.size(); ++i){
seg_occ->free_segment(segments_numbers[i]);
}
}
segment_occupation* mem_ctrl::get_segment_occupation(){
return seg_occ;
}
\ No newline at end of file
/*
Here classes can be found that control the memory occupation on each AWG module.
*/
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
class segment_occupation
{
std::map<int, int> *memory_layout;
std::map<int, std::vector<int>> seg_data;
std::vector<int> memory_sizes;
std::vector<int> index_info;
public:
segment_occupation(std::map<int, int>* mem_layout);
std::pair<int, int> request_new_segment(int size);
void free_segment(int seg_number);
std::vector<int> *get_memory_sizes();
std::map<int, std::vector<int>> *get_seg_data();
};
class mem_ctrl
{
std::map<int, int> memory_layout;
segment_occupation* seg_occ;
public:
mem_ctrl();
~mem_ctrl();
std::pair<int, int> get_upload_slot(int n_points);
void release_memory(std::vector<int> segments_numbers);
segment_occupation* get_segment_occupation();
};
\ No newline at end of file
This diff is collapsed.
File deleted
from libcpp cimport bool
from libc.stdlib cimport malloc, free
from libcpp.vector cimport vector
from libcpp.string cimport string
from libcpp.pair cimport pair
from libcpp.map cimport map as mapcpp
from cython.operator import dereference, postincrement
import numpy as np
cimport numpy as np
cdef extern from "keysight_awg_post_processing_and_upload.h":
struct waveform_raw_upload_data:
vector[double*] *wvf_data
# npt added here already as you cannot fetch it without the gil
vector[int] *wvf_npt
pair[double, double] *min_max_voltage
short *upload_data
int *npt
vector[int] data_location_on_AWG
ctypedef waveform_raw_upload_data* waveform_raw_upload_data_ptr
cdef extern from "keysight_awg_post_processing_and_upload.h":
cdef cppclass cpp_uploader:
cpp_uploader() except +
void add_awg_module(string name, int chassis, int slot) nogil
void add_upload_job(mapcpp[string, mapcpp[int, waveform_raw_upload_data_ptr]] *upload_data) nogil
void release_memory(mapcpp[string, mapcpp[int, waveform_raw_upload_data_ptr]] *upload_data) nogil
void resegment_memory() nogil
cdef struct s_waveform_info:
pair[double, double] min_max_voltage
double integral
ctypedef s_waveform_info waveform_info
cdef class keysight_upload_module():
"""
This is a speraturate module for the upload in keysight units. This module also does some last post processing on the waveform (e.g. DSP/convert to short/extend them, so they fit into the memory ...)
The upload in writtin in C, so can be fully run without gil in a multhithreaded way.
This module is in now way a replacement for the python module and has as main function to get the waveform into the memory
"""
cdef cpp_uploader *keysight_uploader
def __cinit__(self):
self.keysight_uploader = new cpp_uploader()
cdef mapcpp[string, pair[string, int]] channel_to_AWG_map
def add_awg_module(self, name, module):
'''
add an AWG module to the keysight object.
Args:
module (qCodeS driver) : qcodes object of the AWG
'''
cdef string c_name = name.encode('utf8')
self.keysight_uploader.add_awg_module(c_name, module.chassis, module.slot)
def add_upload_data(self, waveform_cache_container waveform_cache):
cdef mapcpp[string, mapcpp[int, waveform_raw_upload_data_ptr]] *AWG_raw_upload_data
AWG_raw_upload_data = &waveform_cache.AWG_raw_upload_data
with nogil:
self.keysight_uploader.add_upload_job(AWG_raw_upload_data)
AWG_init_data = dict()
cdef mapcpp[string, pair[string, int]].iterator it = waveform_cache.channel_to_AWG_map.begin()
cdef waveform_raw_upload_data_ptr channel_data
while(it != waveform_cache.channel_to_AWG_map.end()):
# make tuple with gate voltages for the channel and location of the AWG memeory where the waveforms are stored.
channel_data = dereference(dereference(AWG_raw_upload_data.find(dereference(it).second.first)).second.find(dereference(it).second.second)).second
min_max_voltage = (channel_data.min_max_voltage.first, channel_data.min_max_voltage.second,)
# TODO format as (memory_upload_location, cycles, prescalor) (last two not implented in c++ upload part)
upload_locations = channel_data.data_location_on_AWG
AWG_init_data[dereference(it).first] = (min_max_voltage, upload_locations)
postincrement(it)
return AWG_init_data
def resegment_memory(self):
'''
Apply a full re-segmentation of the memory of the AWG.
Means a full flush and also remaking of the memory table (might take a while...)
'''
self.keysight_uploader.resegment_memory()
def release_memory(self, waveform_cache_container waveform_cache):
cdef mapcpp[string, mapcpp[int, waveform_raw_upload_data_ptr]] *AWG_raw_upload_data
AWG_raw_upload_data = &waveform_cache.AWG_raw_upload_data
self.keysight_uploader.release_memory(AWG_raw_upload_data)
cdef class waveform_cache_container():
cdef mapcpp[string, pair[string, int]] channel_to_AWG_map
cdef mapcpp[string, mapcpp[int, waveform_raw_upload_data_ptr]] AWG_raw_upload_data
cdef dict waveform_chache_python
def __init__(self, channel_to_AWG_map_py, voltage_limits):
'''
Initialize the waveform cache.
Args:
channel_to_AWG_map_py (dict<str channel_name, tuple<str AWG_name, int channel_number>)
voltage_limits (dict<str channel_name, typle<double min_voltage, double max_voltage>)
'''
self.waveform_chache_python = dict()
for key, value in channel_to_AWG_map_py.items():
new_value = (value[0].encode(), value[1])
self.channel_to_AWG_map[key.encode()] = new_value
waveform_cache = waveform_upload_chache(voltage_limits[key])
self.waveform_chache_python[key] = waveform_cache
self.AWG_raw_upload_data[new_value[0]][new_value[1]] = waveform_cache.return_raw_data()
def __getitem__(self, str key):
'''
get waveform_upload_chache object
Args:
key (str) : name of the channel
'''
return self.waveform_chache_python[key]
@property
def npt(self):
'''
Return the number of point that is saved in the caches (= total number of points that need to be uploaded).
Note that it is assumed that you run this function when all the caches have been populated and have similar size.
If you want to know npt per chache, you should call self['channel_name'].npt
'''
if len(self.waveform_chache_python) == 0 :
raise ValueError("No waveforms presents in waveform chache container ...")
# get first key of chache object (the pyton one)
idx = next(iter(self.waveform_chache_python))
return self[idx].npt
def generate_DC_compenstation(self, sample_rate):
'''
generate a DC compensation of the pulse.
As assuallly we put condensaters in between the AWG and the gate on the sample, you need to correct for the fact that the low fequencies are not present in your transfer function.
This can be done simply by making the total integral of your function 0.
Args:
sample_rate (float) : rate at which the AWG runs.
'''
cdef int compensation_npt = 0
cdef int wvf_npt = 0
for chan in self.waveform_chache_python:
if int(self[chan].compensation_time*sample_rate +0.99) > compensation_npt:
compensation_npt = int(self[chan].compensation_time*sample_rate +0.99)
wvf_npt = self[chan].npt
# make sure we have modulo 10 time
cdef int total_pt = compensation_npt + wvf_npt
cdef int mod = total_pt%10
if mod != 0:
total_pt += 10-mod
compensation_npt = total_pt - wvf_npt
#generate the compensation
for chan in self.waveform_chache_python:
self[chan].generate_voltage_compensation(compensation_npt, sample_rate)
cdef class waveform_upload_chache():
"""object that holds some cache for uploads (for a single channel) and does some basic calculations"""
cdef waveform_raw_upload_data raw_data
cdef vector[waveform_info] wvf_info
cdef vector[double *] wvf_data
cdef vector[int] wvf_npt
cdef pair[double, double] min_max_voltage
cdef pair[double, double] compenstation_limit
cdef int _npt
cdef list data
def __init__(self, compenstation_limit):
self.compenstation_limit = compenstation_limit
self._npt = 0
self.data = []
def add_data(self, np.ndarray[np.double_t,ndim=1] wvf, v_min_max, integral):
'''
wvf (np.ndarray[ndim = 1, dtype=double]) : waveform
v_min_max (tuple) : maximum/minimum voltage of the current segment
integral (double) : integrated value of the waveform
'''
cdef waveform_info data_info
if self._npt == 0:
self.min_max_voltage = v_min_max
else:
if self.min_max_voltage.first > v_min_max[0]:
self.min_max_voltage.first = v_min_max[0]
if self.min_max_voltage.second < v_min_max[1]:
self.min_max_voltage.second = v_min_max[1]
self._npt += wvf.size
data_info.min_max_voltage = v_min_max
data_info.integral = integral
self.wvf_info.push_back(data_info)
cdef double* my_data = <double*> wvf.data
self.wvf_data.push_back(my_data)
# use dummy python list for reference counting ..
self.data.append(wvf)
self.wvf_npt.push_back(wvf.size)
@property
def integral(self):
cdef double integral = 0
for i in self.wvf_info:
integral += i.integral
return integral
@property
def compensation_time(self):
'''
return the minimal compensation time that is needed.
Returns:
compensation_time : minimal duration that is needed for the voltage compensation
'''
if self.compenstation_limit.first == 0 and self.compenstation_limit.second == 0:
return 0.
if self.integral <= 0:
return -self.integral / self.compenstation_limit.second
else:
return -self.integral / self.compenstation_limit.first
@property
def npt(self):
return self._npt
cpdef void generate_voltage_compensation(self, int npt, double sample_rate):
'''
make a voltage compenstation pulse of time t
Args:
npt (double) : number of points allocated for the compenstation pulse
sample_rate (double) : rate at which the pulse is generated.
'''
cdef double voltage = 0
if npt == 0 or self.compensation_time == 0:
voltage = 0
else:
voltage = -self.integral*sample_rate/npt
pulse = np.full((npt+10,), voltage)
pulse[-10:] = 0 #add 10 points extra to make sure you end up with 0V when done.
self.add_data(pulse, (voltage, voltage), -self.integral)
cdef waveform_raw_upload_data* return_raw_data(self):
self.raw_data.wvf_data = &self.wvf_data
self.raw_data.wvf_npt = &self.wvf_npt
self.raw_data.min_max_voltage = &self.min_max_voltage
self.raw_data.npt = &self._npt
return &self.raw_data
import pulse_lib.segments.segment_container
from qcodes import Parameter
from pulse_lib.keysight.M3202A_uploader import convert_prescaler_to_sample_rate
from pulse_lib.segments.utility.data_handling_functions import find_common_dimension, update_dimension
from pulse_lib.segments.utility.setpoint_mgr import setpoint_mgr
from pulse_lib.segments.utility.looping import loop_obj
from pulse_lib.keysight.uploader import convert_prescaler_to_sample_rate
from pulse_lib.segments.data_classes.data_HVI_variables import marker_HVI_variable
from pulse_lib.segments.data_classes.data_generic import data_container, parent_data
......
......@@ -24,28 +24,6 @@ extensions = [
include_dirs=[numpy.get_include()],
),
]
if os.name == 'nt':
extensions += [Extension("pulse_lib.keysight.uploader_core.uploader",
sources = ["pulse_lib/keysight/uploader_core/uploader.pyx",
"pulse_lib/keysight/uploader_core/mem_ctrl.cpp",
"pulse_lib/keysight/uploader_core/keysight_awg_post_processing_and_upload.cpp"],
include_dirs=[numpy.get_include(),"C://Program Files (x86)//Keysight//SD1"],
libraries = ["keysightSD1"],#["SD1core", "SD1pxi"],
library_dirs =["C://Program Files (x86)//Keysight//SD1//shared//", "C://Program Files (x86)//Keysight//SD1//Libraries//libx64//Cpp_MSVC2008"],
language='c++',
extra_compile_args=['/openmp'],
) ]
# else:
# extensions += [Extension("pulse_lib.keysight.uploader_core.uploader",
# sources = ["pulse_lib/keysight/uploader_core/uploader.pyx",
# "pulse_lib/keysight/uploader_core/mem_ctrl.cpp",
# "pulse_lib/keysight/uploader_core/keysight_awg_post_processing_and_upload.cpp"],
# include_dirs=[numpy.get_include(),"/usr/local/include/Keysight/SD1/cpp", "/usr/local/include/Keysight/SD1/common"],
# libraries=["SD1core", "SD1pxi", "gomp"],
# library_dirs=["/usr/local/lib/Keysight/SD1/"],
# language='c++',
# extra_compile_args=['-fopenmp'],
# ) ]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment