首页/文章/ 详情

作为射频工程师的你,是不是对EVM的计算方法也感点兴趣?

12月前浏览2184
(1)

在EVM是个什么东东喽?,有说过,EVM衡量的是星座图上理想点和实测点偏移量的大小。

虽说,有这样一个公式,但是光看公式,总是觉得云里雾里的。

最近,因为在琢磨用ADS进行系统仿真的事,然后一步一步下来,发现在ADS的AEL文件里,藏着很多宝藏。

昨天说过,在射频层面仿真EVM的时候,在ADS里面发现两种仿真计算的方法。

一种是在dds文件上,以constellation()函数为基础,一个一个写公式,然后出来EVM的计算结果。

另一种,是在dds文件上,直接调用const_evm()函数来计算EVM的值。

我大概过了一下两种方法的流程,大致差不多。

但是,昨天在仿真的时候,发现当我把Symbol_rate从24.3KHz调整到150KHz,并且输入功率设小的时候,两者仿真出来的结果差很多。

没办法,只能仔细地去看const_evm()函数里面的具体细节。

(2)

先把这个程序贴出来。


































































































defun const_evm(vfund_ideal, vfund_dist, u_symbol_rate,     u_sample_delay, u_rotation, u_transient_duration, u_path_delay){
   if ( vfund_ideal == NULL || vfund_dist == NULL )   return;
 decl symbol_rate = u_symbol_rate == NULL ? 0 : u_symbol_rate;    decl transient_duration = u_transient_duration == NULL ? 0 : u_transient_duration;  decl tran_time = 0;                          if ( transient_duration )                     // skip integer number of symbols    tran_time = ceil(mag(transient_duration)*symbol_rate)/symbol_rate;    decl sample_delay = u_sample_delay == NULL ? best_delay_1d(vfund_ideal,symbol_rate,tran_time) : u_sample_delay;
   decl rotation =  u_rotation == NULL ? 0 : u_rotation;  decl Path_Delay_default = delay_path(vfund_ideal,vfund_dist);    decl path_delay = u_path_delay == NULL ? Path_Delay_default[0] : u_path_delay;   // This is a better default than 0.
   vfund_ideal = vfund_ideal*exp(j*rotation);  vfund_dist = vfund_dist*exp(j*rotation);    decl vfund_ideal_real = real(vfund_ideal);    decl vfund_ideal_imag = imag(vfund_ideal);  decl vfund_dist_real = real(vfund_dist);    decl vfund_dist_imag = imag(vfund_dist);  decl time_values = indep(vfund_ideal);  decl time_step = time_values[1]-time_values[0];    decl i;  decl numPts_i=9;  decl min_time_offset = -0.5*time_step;  decl time_offset;  decl best_evm_rms = 1000;  // initialized as an arbitrary large number  decl evm_rms_this_trial;  decl evm_percent;  decl best_mean_phase_delta;  decl best_vfund_C_dist;  decl best_evm;
   // Ideal trajectory    decl ideal_trajectory = vs(vfund_ideal_imag, vfund_ideal_real);
   // set interpolation points    decl start_time = sample_delay;                                 // removed + tran_time;    decl stop_time = max(indep(vfund_ideal));    decl incr = NULL;    if (symbol_rate)    incr = 1/symbol_rate;
   // Ideal constellation    decl vfund_C_ideal_real = interp(vfund_ideal_real, start_time, stop_time, incr);    decl vfund_C_ideal_imag = interp(vfund_ideal_imag, start_time, stop_time, incr);    decl vfund_C_ideal = vfund_C_ideal_real+j*vfund_C_ideal_imag;  decl vfund_C_ideal_rms = sqrt(mean(mag(vfund_C_ideal)**2));  vfund_C_ideal = vfund_C_ideal/vfund_C_ideal_rms;    decl ideal_constellation = vs(imag(vfund_C_ideal), real(vfund_C_ideal));    set_attr(ideal_constellation,"TraceType","Scatter");
   for (i=0; i<numPts_i; i++)  // Loop to find minimum EVM  {    time_offset = min_time_offset + i*time_step/8;    decl vfund_C_dist_real = interp(vfund_dist_real, start_time+path_delay+time_offset, stop_time, incr);    // this length may change with i.    decl vfund_C_dist_imag = interp(vfund_dist_imag, start_time+path_delay+time_offset, stop_time, incr);    // this length may change with i.    decl vfund_C_dist = vfund_C_dist_real +j*vfund_C_dist_imag;
   decl sn = min([sweep_size(vfund_C_dist_real), sweep_size(vfund_C_ideal_real)]);
   // phase compensation    decl mean_phase_delta = __phase_delta_calc(vfund_C_ideal[0::sn-1], vfund_C_dist[0::sn-1]);    vfund_C_dist =  (vfund_C_dist)*exp(-j*mean_phase_delta);
   // scaling    decl scaling = sqrt(mean(mag(vfund_C_dist[0::sn-1])**2));   // Previously divided this by RMS value of ideal constellation, but this is now 1.    vfund_C_dist = vfund_C_dist/scaling;
   // EVM    decl evm = mag(vfund_C_dist[0::sn-1]-vfund_C_ideal[0::sn-1]);
   // EVM_percent    evm_rms_this_trial = sqrt(mean(evm**2));   // This could be divided by the RMS value of the ideal constellation, but this is now 1.    if (evm_rms_this_trial < best_evm_rms)    {      best_evm = evm;      best_evm_rms = evm_rms_this_trial;      best_mean_phase_delta = mean_phase_delta;      best_vfund_C_dist = vfund_C_dist*scaling;           // undoing the scaling to generate the distorted constellation    }    evm_percent = 100*best_evm_rms; // has to be calculated inside the loop because sn could change from one iteration to the next.  }
   // distorted compensated trajectory (phase compensation only)  vfund_dist=vfund_dist*exp(-j*best_mean_phase_delta);    decl dist_trajectory = vs(imag(vfund_dist),real(vfund_dist));
   decl distorted_constellation = vs(imag(best_vfund_C_dist), real(best_vfund_C_dist));    set_attr(distorted_constellation,"TraceType","Scatter");
   return list(ideal_constellation, ideal_trajectory, distorted_constellation, dist_trajectory, best_evm, evm_percent);} //fun - const_evm

看上去很长也很难懂,对不对?我也是鼓足很大的勇气,才对着电脑开始研读的。

但是等看完了以后,发现,以后不能自己吓自己。这个程序看上去很长,但是其实只要懂点英文,然后有点数学基础,基本都能看懂。

对于我来说,这上面最大的不懂点,可能就是这条语句。但是现在有AI,分分钟给你解释的清清楚楚。


decl symbol_rate = u_symbol_rate == NULL ? 0 : u_symbol_rate;

比如说,上面这条语句,chatgpt是这样给我解释的。

简单理解,就是把u_symbol_rate的值,赋给symbol_rate。

接着,我们一条条看一下const_evm()中的语句哈!

(3)















if ( vfund_ideal == NULL || vfund_dist == NULL )   return;
 decl symbol_rate = u_symbol_rate == NULL ? 0 : u_symbol_rate;    decl transient_duration = u_transient_duration == NULL ? 0 : u_transient_duration;  decl tran_time = 0;                          if ( transient_duration )                     // skip integer number of symbols    tran_time = ceil(mag(transient_duration)*symbol_rate)/symbol_rate;    decl sample_delay = u_sample_delay == NULL ? best_delay_1d(vfund_ideal,symbol_rate,tran_time) : u_sample_delay;
   decl rotation =  u_rotation == NULL ? 0 : u_rotation;  decl Path_Delay_default = delay_path(vfund_ideal,vfund_dist);    decl path_delay = u_path_delay == NULL ? Path_Delay_default[0] : u_path_delay;   // This is a better default than 0.

decl,从ADS的help文件可知,是define a variable的意思。

在dds文件中,使用const_evm的时候,会给参数,如下图所示。

所以,上面的语句,主要就是把用户给的参数传递给函数内部。

注意一下,这里有个delay_path()这个函数。为啥提醒注意呢?前面不是说,发现两种仿真计算下,结果有时候会不一样么?其中一个因素就是这个。
在const_evm中,计算失真信号的星座图的时候,把链路的延时考虑进去的;但是另一种计算过程中,则没有。

(4)






 vfund_ideal = vfund_ideal*exp(j*rotation); vfund_dist = vfund_dist*exp(j*rotation); decl vfund_ideal_real = real(vfund_ideal); decl vfund_ideal_imag = imag(vfund_ideal); decl vfund_dist_real = real(vfund_dist); decl vfund_dist_imag = imag(vfund_dist);

在const_evm()的参数中,第一个参数,是理想的信号;第二个参数,是经过射频链路后的信号,暂且把它称为失真信号。


上面的语句,主要是把理想信号(vfund_ideal)和失真信号(vfund_dist)都旋转一个角度,这个角度也是在const_evm()的参数中定义。然后取出旋转后信号的实部和虚部。


可以看到,这个旋转对理想信号和失真信号是同时有效的。所以,还没搞清楚,这个参数的主要的作用是啥。不过呢,这个参数主要是影响星座图的旋转角度,和EVM的值,倒是没啥关系。


(5)













  decl time_values = indep(vfund_ideal);  decl time_step = time_values[1]-time_values[0];    decl i;  decl numPts_i=9;  decl min_time_offset = -0.5*time_step;  decl time_offset;  decl best_evm_rms = 1000;  // initialized as an arbitrary large number  decl evm_rms_this_trial;  decl evm_percent;  decl best_mean_phase_delta;  decl best_vfund_C_dist;  decl best_evm;

因为仿真结果是基于envelope仿真器的,有Tstep设置。所以上面的第一和第二句,就是从信号中恢复出这个Tstep。

接下来的语句,是定义各种各样的变量。


(6)



    // Ideal trajectory    decl ideal_trajectory = vs(vfund_ideal_imag, vfund_ideal_real);

这个语句厉害了,没看到这个语句的时候,我觉得,把星座图画出来好难啊!等我看到这个语句的时候,一下子无言,就这么一句话!当然,这里还不是星座图,是符号变化的轨迹。不过离星座图就差一行代码!


不过,我个人觉得这里有个小bug。

因为仿真过程中发现,在dds文件上,想把理想信号的星座图和轨迹放在一张图上的话,轨迹变成一个很小的点。

从代码来看,应该是因为计算星座图的时候,归一化了,但是看计算轨迹的时候,则没有。

再说回上面的语句。

看help文件,vs()是这么解释的。

看上去不太直观哈,但是从最终绘制的结果来看,大概就是在二维矩形坐标上,画出dependent随independent变化的曲线。

(7)
















 // set interpolation points    decl start_time = sample_delay;                                 // removed + tran_time;    decl stop_time = max(indep(vfund_ideal));    decl incr = NULL;    if (symbol_rate)    incr = 1/symbol_rate;
   // Ideal constellation    decl vfund_C_ideal_real = interp(vfund_ideal_real, start_time, stop_time, incr);    decl vfund_C_ideal_imag = interp(vfund_ideal_imag, start_time, stop_time, incr);    decl vfund_C_ideal = vfund_C_ideal_real+j*vfund_C_ideal_imag;  decl vfund_C_ideal_rms = sqrt(mean(mag(vfund_C_ideal)**2));  vfund_C_ideal = vfund_C_ideal/vfund_C_ideal_rms;    decl ideal_constellation = vs(imag(vfund_C_ideal), real(vfund_C_ideal));    set_attr(ideal_constellation,"TraceType","Scatter");


在help文件中,interp()的解释是这样的。

信号的时间间隔原来是Tstep,这个我在原理图中设置的是1/(4*symbolrate),在程序中,做了个线性插值,使得信号的时间间隔变为1/symbolrate。


然后对理想信号进行归一化,并用vs()画出在二维坐标系上,虚部和实部的关系。并且用set_attr()来设置图形的表现形式,让其以点状的形式显示,从而显示成星座图,而不是轨迹。


(8)
































 for (i=0; i<numPts_i; i++)  // Loop to find minimum EVM  {    time_offset = min_time_offset + i*time_step/8;    decl vfund_C_dist_real = interp(vfund_dist_real, start_time+path_delay+time_offset, stop_time, incr);    // this length may change with i.    decl vfund_C_dist_imag = interp(vfund_dist_imag, start_time+path_delay+time_offset, stop_time, incr);    // this length may change with i.    decl vfund_C_dist = vfund_C_dist_real +j*vfund_C_dist_imag;
   decl sn = min([sweep_size(vfund_C_dist_real), sweep_size(vfund_C_ideal_real)]);        // phase compensation    decl mean_phase_delta = __phase_delta_calc(vfund_C_ideal[0::sn-1], vfund_C_dist[0::sn-1]);    vfund_C_dist =  (vfund_C_dist)*exp(-j*mean_phase_delta);        // scaling    decl scaling = sqrt(mean(mag(vfund_C_dist[0::sn-1])**2));   // Previously divided this by RMS value of ideal constellation, but this is now 1.    vfund_C_dist = vfund_C_dist/scaling;        // EVM    decl evm = mag(vfund_C_dist[0::sn-1]-vfund_C_ideal[0::sn-1]);
   // EVM_percent    evm_rms_this_trial = sqrt(mean(evm**2));   // This could be divided by the RMS value of the ideal constellation, but this is now 1.    if (evm_rms_this_trial < best_evm_rms)    {      best_evm = evm;      best_evm_rms = evm_rms_this_trial;      best_mean_phase_delta = mean_phase_delta;      best_vfund_C_dist = vfund_C_dist*scaling;           // undoing the scaling to generate the distorted constellation    }    evm_percent = 100*best_evm_rms; // has to be calculated inside the loop because sn could change from one iteration to the next.  }

这个for语句,是代码里面做了一个循环,以time_step/8为步进,来找出最合适的采样点,使得EVM最小。









 // phase compensation    decl mean_phase_delta = __phase_delta_calc(vfund_C_ideal[0::sn-1], vfund_C_dist[0::sn-1]);    vfund_C_dist =  (vfund_C_dist)*exp(-j*mean_phase_delta);        // scaling    decl scaling = sqrt(mean(mag(vfund_C_dist[0::sn-1])**2));   // Previously divided this by RMS value of ideal constellation, but this is now 1.    vfund_C_dist = vfund_C_dist/scaling;

这四个语句,分别是进行相位补偿和幅度归一化。







// EVM    decl evm = mag(vfund_C_dist[0::sn-1]-vfund_C_ideal[0::sn-1]);

这个呢,就是开始计算EVM呢,即理想矢量点和实测矢量点之间的误差的幅值。


(9)









    // distorted compensated trajectory (phase compensation only)    vfund_dist=vfund_dist*exp(-j*best_mean_phase_delta);    decl dist_trajectory = vs(imag(vfund_dist),real(vfund_dist));
   decl distorted_constellation = vs(imag(best_vfund_C_dist), real(best_vfund_C_dist));    set_attr(distorted_constellation,"TraceType","Scatter");        return list(ideal_constellation, ideal_trajectory, distorted_constellation, dist_trajectory, best_evm, evm_percent);


最后,就是计算失真信号的星座图和变化轨迹啦!

(10)

终于讲完了!



来源:加油射频工程师
系统仿真ADSUGUM
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2023-12-02
最近编辑:12月前
加油射频工程师
分享所学知识
获赞 246粉丝 83文章 566课程 1
点赞
收藏
未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习 福利任务 兑换礼品
下载APP
联系我们
帮助与反馈