在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的时候,会给参数,如下图所示。
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)
终于讲完了!