| Thread Links | Date Links | ||||
|---|---|---|---|---|---|
| Thread Prev | Thread Next | Thread Index | Date Prev | Date Next | Date Index |
|
Hi Adam,
George got a point, but Hossein mentioned that he saw a significant higher value of COM when changing the sample_adjustment from [-24 24] to [-32 32], which got me thinking.
May we should consider adding an "edge-detect mechanism" as a safety enhancement on top of your Local Search itick pruning? Please review the attached optimize_fom.m (rename _ItickPruningEdgeDetect.txt to .m), will need to clean it a but though.
At the present, it only evaluates iticks within +-LOCAL_SEARCH of the current best itick.
=> For example, BEST.itick= -6 and LOCAL_SEARCH= 2
=> Then only these itick are evaluated: [-8 -7 -6 -5 -4].
=> After evaluating the window, the BEST.itick= -8 and now it landed on the edge of the allowed window.
=> The FOM may still improve if we move to the left, but the optimizer does not know whether -8 is really optimal or whether -9, -10 or -11 would be even better.
The proposed edge-detect mechanism monitors whether the winning sampling phase lands on the boundary of the Local Search window. When an edge hit occurs, the search radius is automatically expanded for the next sweep, reducing the risk of missing an optimum
FOM that has migrated outside the current search range.
Importantly, a single edge hit does not permanently enlarge all future searches. The expansion only applies to the subsequent sweep, after which the algorithm returns to the nominal Local Search radius unless another edge hit is detected.
I am evaluating the 684 cases to evaluate the itick pruning with and without the edge detect.
Regards,
Hansel D'SIlva
Standards Development Engineer- Amphenol
From: George Zimmerman <george@xxxxxxxxxxxxxxxxxxxx>
Sent: Tuesday, September 1, 2026 8:20 PM To: STDS-802-3-COM@xxxxxxxxxxxxxxxxx <STDS-802-3-COM@xxxxxxxxxxxxxxxxx> Subject: [802.3_COM] local optimized phase search CAUTION: EXTERNAL EMAIL
I believe the situation being discussed on the call where the phase search appeared ill-behaved was one where the optimum sampling phase was not within the initial sweep or a valid FOM had not yet been found (e.g., because the sweep was too coarse). The local
search shoudl behave similar to acquisition and tracking of sampling phase. Once you find a valid sampling phase, you generally don't lose it except in very pathological channels (e.g., with unterminated stubs, which we generally don't see). Such channels
would be problematic anyways, as tracking loops would also get stuck in these local minima.
Note that slide 3 of the presentation (https://www.ieee802.org/3/ad_hoc/COM/public/telecon/260901/gregory_COM_01_260901.pdf)
states the requirements below and specifically warns against this. The resolution of the initial sweep needs to be sufficient to sample the open eye (which I believe is needed for a valid FOM, if I understand the statement below correctly)
The text from slide 3 is below. It may be useful to consider better guidance on just what "far away" is.
-george
(from slide 3)
It requires:
• Valid FOM has been found
• Local Search is enabled
• One full sweep of sampling phase has finished
If the current sample phase is far away from the best sample phase, don’t run it. Far away is defined by the Local Search parameter, which is usually set to 2
George Zimmerman, Ph.D. President & Principal CME Consulting, Inc. Experts in Advanced PHYsical Communications 310-920-3860
To unsubscribe from the STDS-802-3-COM list, click the following link: https://listserv.ieee.org/cgi-bin/wa?SUBED1=STDS-802-3-COM&A=1 To unsubscribe from the STDS-802-3-COM list, click the following link: https://listserv.ieee.org/cgi-bin/wa?SUBED1=STDS-802-3-COM&A=1 |
function result=optimize_fom(OP, param, chdata, sigma_bn,do_C2M)
%% License Notice
%
% Copyright 2025 802-COM Authors
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are
% met:
%
% - Redistributions of source code must retain the above copyright
% notice, this list of conditions and the following disclaimer.
%
% - Redistributions in binary form must reproduce the above copyright
% notice, this list of conditions and the following disclaimer in the
% documentation and/or other materials provided with the distribution.
%
% - Neither the name of the copyright holder nor the names of its
% contributors may be used to endorse or promote products derived from
% this software without specific prior written permission.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
% HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
% LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%
% SPDX-License-Identifier: BSD-3-Clause
%% input
% OP: struct with operational parameters
% param: struct with configuration parameters
% chdata: holds channel frequency responses and impulse response
% sigma_bn: noise input used for RX_Calibration
% do_C2M: set to 0 for standard optimize_fom. set to 1 for optimize_fom_for_C2M
%% output
% result: struct holding all the settings associated with the best FOM (EQ, noise, etc...)
%% Initialize Loop Struct called "THIS" that will hold all settings for the current EQ loop
THIS = OptFom_Initialize_Loop_Struct();
%% Initialize parameters
f=chdata(1).faxis;
%Read user input of ts_sample_adj_range
%if one value was entered, go from 0 to that value
%if 2 values were entered, go from the 1st value to the 2nd value
if length(param.ts_sample_adj_range)==1
param.ts_sample_adj_range(2)=param.ts_sample_adj_range(1);
param.ts_sample_adj_range(1)=0;
end
full_sample_range=param.ts_sample_adj_range(1):param.ts_sample_adj_range(2);
% Do not allow Local Search and Box Search. Revert TS SRCH Mode to full-sweep
if param.LOCAL_SEARCH > 0 && strcmpi(OP.TS_SRCH_MODE, 'box')
OP.TS_SRCH_MODE = 'full-sweep';
end
% For Box Search, make the total number of sample point sweeps divisible by the box size
if strcmpi(OP.TS_SRCH_MODE, 'box')
L = length(full_sample_range);
required_num_points = ceil(L/OP.itick_box_size)*OP.itick_box_size;
if L < required_num_points
num_extra_points = required_num_points - L;
extra_points = (1:num_extra_points)+full_sample_range(end);
full_sample_range = [full_sample_range extra_points];
end
end
param.ndfe_passed=param.ndfe;
% param.N_bmax is param.ndfe if groups are not used
if(param.Floating_DFE), param.ndfe=param.N_bmax; end
if OP.RxFFE && strcmp(OP.FFE_OPT_METHOD,'MMSE')
OP.RxFFE_with_MMSE = 1;
else
OP.RxFFE_with_MMSE = 0;
end
Gffe_values = param.cursor_gain;
if ~OP.RxFFE
Gffe_values=0;
end
switch param.CTLE_type
case {'CL93' 'CL120e'}
param.g_DC_HP_values = 0;
end
lf_indx = length(param.g_DC_HP_values);
BEST.FOM = -inf;
BEST.cursor_i = [];
BEST.itick = []; % itick edge case
itick_edge_hit = false; % itick edge case
sbr = [];
if OP.DISPLAY_WINDOW
hwaitbar=waitbar(0);
else
fprintf('FOM search ');
end
%% T_O
if do_C2M
loop_count=[1 2];
T_O=floor((param.T_O/1000)*param.samples_per_ui);
T_O=max(0,T_O);
else
loop_count=1;
T_O=0;
end
%% Check if the speed_up flag is set
if OP.Optimize_loop_speed_up == 1
OP.BinSize = 1e-4;
OP.impulse_response_truncation_threshold = 1e-3;
end
%% Used to speed up FFE by only performing circshift when necessary
pulse_struc(1).pulse_ctle_circshift=[];
ctle_response_updated=1;
%% Build txffe values dynamically
[txffe_matrix, cur, txffe_sweep_indices, FULL_tx_index_vector, txffe_cursor_vector] = OptFom_Build_TXFFE(param);
num_txffe_runs = size(txffe_matrix,1);
% Set the final cursor_index into param struct
param.cursor_index = cur;
%% Calculate loop independent Settings: phase_memory, qual, H_r, etc...
SETTINGS = OptFom_Calculate_Settings(txffe_matrix, chdata, param, OP);
%% if LOCAL_SEARCH> 0
if param.LOCAL_SEARCH> 0
FOM_history = [];
iter_count = 0;
end
%% EQ Loop
runs=length(param.ctle_gdc_values)*lf_indx*length(Gffe_values)*num_txffe_runs;
progress_interval=0.025;
pxi=0;
old_loops=0;
new_loops=0;
itick_skips=0;
itick_cases=0;
edge_hit_count=0;
FOM_TRACKER(1:length(Gffe_values),1:length(param.ctle_gdc_values),1:lf_indx,1:num_txffe_runs,1:length(full_sample_range))=0;
% turn on this debug flag "plot_iticks" to get a plot of FOM for every itick loop
plot_iticks = 0;
if plot_iticks
figure; AxIick = axes; hold on;
end
for i=loop_count
for Gffe_index=1:length(Gffe_values)
param.current_ffegain=Gffe_values(Gffe_index);
for ctle_index=1:length(param.ctle_gdc_values)
%% CTLE Gain
THIS.ctle_index = ctle_index;
THIS.g_dc = param.ctle_gdc_values(ctle_index);
CTLE_fp1 = param.CTLE_fp1(ctle_index);
CTLE_fp2 = param.CTLE_fp2(ctle_index);
CTLE_fz = param.CTLE_fz(ctle_index);
% HF Boost
ctle_gain = FD_CTLE(f, CTLE_fz, CTLE_fp1, CTLE_fp2, THIS.g_dc);
% Mid Frequency Boost (obsolete not used)
ctle_gain_xc = FD_CTLE(SETTINGS.f_xc, CTLE_fz, CTLE_fp1, CTLE_fp2, THIS.g_dc);
for g_LP_index=1:lf_indx
%% Apply CTLE to impulse response
THIS.g_DC_low = param.g_DC_HP_values(g_LP_index);
THIS.g_LP_index = g_LP_index;
%GDC Qual Check
if SETTINGS.qual(g_LP_index,ctle_index)==0
pxi=pxi+num_txffe_runs;
continue;
end
% set the flag to show ctle response was updated (used to speed up TxFFE)
if OP.INCLUDE_CTLE==1
ctle_response_updated=1;
end
[chdata, THIS.H_ctf, H_low_xc, H_ctf2] = OptFom_Compute_CTLE(chdata, ctle_gain, THIS, SETTINGS.f_xc, param, OP);
%% Calculate noise parameters that do not depend on TxFFE, RxFFE, DFE
Noise_XC = OptFom_Calc_Noise_XC(H_low_xc, ctle_gain_xc, SETTINGS, param, OP);
% RIM 11-30-2020 moved to a subfunction
[THIS.sigma_N] = get_sigma_eta_ACCM_noise(chdata,param,SETTINGS.H_sy,SETTINGS.H_r,THIS.H_ctf);
if OP.RX_CALIBRATION
THIS.sigma_ne = get_sigma_noise( H_ctf2, param, chdata, sigma_bn); %% Equation 93A-48 %%
sigma_NEXT=sqrt(param.eta_0*sum( abs(SETTINGS.H_sy(2:end).^2 .* SETTINGS.H_r(2:end).*2 .* THIS.H_ctf(2:end).^2 ) .* diff(chdata(1).faxis)/1e9));
else
% Equations 93A-33 and 93A-34 for NEXT - independent of TXFFE setting %%
% sigma_NEXT not used sigma_ne is one used in Rx calibration RIM 03-28-2024
% sigma_NEXT = get_xtlk_noise( [0 1 0], 'NEXT', param, chdata );
THIS.sigma_ne=0;
end
%% Check GDC_MIN violation (change per 0.3k draft 2.3)
if param.GDC_MIN ~= 0 && THIS.g_dc + THIS.g_DC_low > param.GDC_MIN
pxi=pxi+num_txffe_runs;
continue;
end
%% Initial PSD (without TXFFE)
THIS.PSD_results=[];
if OP.RxFFE_with_MMSE
OP.WO_TXFFE=1;
THIS.PSD_results=get_PSDs(THIS.PSD_results,[],[],[],THIS.g_dc,THIS.g_DC_low,param,chdata,OP);
end
%% TXFFE Loop
%Originally this was a separate for loop for each tap, but it is now all contained in the txffe_matrix to use a single modular loop
for TK=1:num_txffe_runs
pxi=pxi+1;
progress = pxi/runs;
if OP.DISPLAY_WINDOW
if ~mod(pxi,floor(runs*progress_interval))
waitbar(progress, hwaitbar, 'Linear equalization tuning'); figure(hwaitbar); drawnow;
end
else
if ~mod(pxi,floor(runs*progress_interval)), fprintf('%i%% ', round(progress*100) );end
end
% Skip combinations with small values of c(0), not guaranteed to be supported by all transmitters.
txffe_cur=txffe_cursor_vector(TK);
if txffe_cur<param.tx_ffe_c0_min
continue;
end
%get the index used for each tap on this iteration (needed for LOCAL SEARCH)
THIS.tx_index_vector=FULL_tx_index_vector(TK,:);
%% LOCAL SEARCH
old_loops=old_loops+1;
if param.LOCAL_SEARCH>0 && ~isinf(BEST.FOM)
iter_count= iter_count + 1;
if param.NonZeroLSMethod== 1
skip_it = OptFom_Adaptive_Local_Search(param.LOCAL_SEARCH, param.Overwrite_Min_Radius, BEST, THIS, FOM_history, iter_count, num_txffe_runs);
else
skip_it = OptFom_Local_Search(param.LOCAL_SEARCH, BEST, THIS, txffe_sweep_indices);
end
if skip_it
continue;
end
end
new_loops=new_loops+1;
%% TXFFE
%fetch txffe for this iteration
THIS.txffe=txffe_matrix(TK,:);
[sbr, chdata, pulse_struc] = OptFom_Compute_TXFFE(chdata, pulse_struc, THIS.txffe, ctle_response_updated, param, OP);
% IMPORTANT: remember sbr just after txffe so it can be restored on each itick loop
sbr_from_txffe=sbr;
% after txffe, ctle updated flag is off until the next CTLE loop
if ctle_response_updated
ctle_response_updated = 0;
end
%% Find Sample Location
[raw_cursor_i, no_zero_crossing, sbr_peak_i] = OptFom_Find_Sample_Point(sbr, param, OP, SETTINGS.Peak_Search_Range);
if no_zero_crossing
continue;
end
triple_transit_time = round(sbr_peak_i*2/param.samples_per_ui)+20;
if SETTINGS.min_number_of_UI_in_response < triple_transit_time
SETTINGS.min_number_of_UI_in_response = triple_transit_time;
end
%% ITICK LOOP
[loop_range, BEST, box_search, cluster, box_mid] = OptFom_Setup_Sampler_Sweep(full_sample_range, BEST, OP);
effective_radius = param.LOCAL_SEARCH;
if itick_edge_hit
effective_radius = 2*param.LOCAL_SEARCH;
end
% reset; this sweep will decide the radius of NEXT sweep
itick_edge_hit = false;
% itick edge case
itick_search_center = [];
if ~isinf(BEST.FOM) && ...
param.LOCAL_SEARCH>0 && ...
~box_search
itick_search_center = BEST.itick;
end
for itickn=loop_range
%% get cursor index for this itick loop
if box_search
[THIS.itick, BEST, skip_it] = OptFom_Itick_BoxSearch(itickn, cluster, BEST, box_mid, OP.itick_box_size);
if skip_it
continue;
end
else
THIS.itick=full_sample_range(itickn);
end
THIS.cursor_i = raw_cursor_i+THIS.itick;
% IMPORTANT: restore sbr on each loop (can't use the one with RxFFE applied)
sbr=sbr_from_txffe;
%% Local Search for +/- itick sweep
% itick_cases=itick_cases+1;
% if ~isinf(BEST.FOM) && param.LOCAL_SEARCH > 0 && ~box_search && max(THIS.g_LP_index, THIS.ctle_index) > 1
% if abs(THIS.itick-BEST.itick) > param.LOCAL_SEARCH
% itick_skips = itick_skips+1;
% continue;
% end
% end
itick_cases=itick_cases+1;
% effective_radius = param.LOCAL_SEARCH;
%
% % Expand search window if previous search hit boundary
% if itick_edge_hit
% effective_radius = 2 * param.LOCAL_SEARCH;
% end
if ~isinf(BEST.FOM) && ...
param.LOCAL_SEARCH > 0 &&...
~box_search &&...
max([THIS.g_LP_index THIS.ctle_index TK]) > 1
if abs(THIS.itick - BEST.itick) > effective_radius %param.LOCAL_SEARCH
itick_skips = itick_skips+1;
continue;
end
end
%% RXFFE: updates C, floating_tap_locations, FOM, PSD_results, and MMSE_results
if OP.RxFFE
[sbr, THIS, skip_it] = OptFom_Compute_RxFFE(sbr, THIS, Noise_XC, chdata, param, OP);
if skip_it
continue;
end
end
%% 93A.1.6 step c defines A_s %%
cursor = sbr(THIS.cursor_i);
THIS.A_p=sbr(sbr_peak_i);
THIS.A_s = param.R_LM*cursor/(param.levels-1);
if isempty(SETTINGS.delta_sbr)
SETTINGS.delta_sbr = sbr;
end
sbr=sbr(:);
%% Equation 93A-27: "otherwise" case
THIS.far_cursors = sbr(THIS.cursor_i-T_O+param.samples_per_ui*(param.ndfe+1):param.samples_per_ui:end);
t=((THIS.cursor_i+param.samples_per_ui*(param.ndfe+1):param.samples_per_ui:length(sbr))-(THIS.cursor_i+param.samples_per_ui*(param.ndfe+1)))*...
param.ui/param.samples_per_ui;
THIS.precursors = sbr(THIS.cursor_i-param.samples_per_ui:-param.samples_per_ui:1);
THIS.precursors = THIS.precursors(end:-1:1);
%% skip this case if FOM has no chance of beating old FOM
%this is also done below but with excess_dfe_cursors included.
%excess_dfe_cursors requires the floating DFE computation which is
%time consuming, so checking here can have significant run time improvements
if ~OP.RxFFE_with_MMSE
sigma_ISI_ignoreDFE = param.sigma_X*norm([THIS.precursors; THIS.far_cursors]);
if (20*log10(THIS.A_s/sigma_ISI_ignoreDFE) < BEST.FOM)
continue
end
end
%% sbr required length = cursor + all DFE UI + 1 additional UI
sbr_required_length=THIS.cursor_i+param.samples_per_ui*(param.ndfe+1);
if length(sbr)<sbr_required_length
sbr(end+1:sbr_required_length)=0;
end
%% Solve DFE: updates dfetaps, floating_tap_coef, tail_RSS, excess_dfe_cursors, and floating_tap_locations
[THIS, param] = OptFom_Compute_DFE(sbr, THIS, param, do_C2M, T_O);
%% Calculate all Noise: updates h_J, sigma_TX, ISI_N, sigma_N, total_noise_rms
[THIS, abort_status] = OptFom_Calc_Noise(THIS, BEST.FOM, sbr, SETTINGS, chdata, param, OP);
if abort_status == 1
continue;
elseif abort_status == 2
break;
end
%% Find FOM (unless using RxFFE with MMSE since FOM has already been found)
if ~OP.RxFFE_with_MMSE
[THIS.FOM, skip_loop] = OptFom_Calc_FOM(chdata, do_C2M, THIS, param, OP, sbr);
if skip_loop
continue;
end
end
if param.LOCAL_SEARCH>0
% Update FOM history for adaptive local search
FOM_history = [FOM_history THIS.FOM];
max_history_length = length(loop_range);
if length(FOM_history) > max_history_length
FOM_history = FOM_history(end-max_history_length+1:end);
end
end
itick_index=find(THIS.itick==full_sample_range);
FOM_TRACKER(Gffe_index,ctle_index,g_LP_index,TK,itick_index)=THIS.FOM;
%% Update Best Settings
BEST = OptFom_Set_Best_Itick(THIS, BEST);
%% Detect if optimum landed on search boundary
if ~isempty(itick_search_center)
if abs(BEST.itick - itick_search_center) == effective_radius %>= effective_radius
itick_edge_hit= true;
edge_hit_count= edge_hit_count + 1;
fprintf(['EDGE HIT: center=%d best=%d ' ...
'radius=%d CTLE=%d LP=%d TX=%d\n'], ...
itick_search_center, ...
BEST.itick, ...
effective_radius, ...
THIS.ctle_index, ...
THIS.g_LP_index, ...
TK);
% else
%
% itick_edge_hit = false;
end
end
if (THIS.FOM > BEST.FOM)
BEST = OptFom_Update_Best_Setttings(BEST, THIS, sbr, chdata, param, OP);
end
end
if plot_iticks
plot(AxIick, full_sample_range,squeeze(FOM_TRACKER(Gffe_index,ctle_index,g_LP_index,TK,:)));
end
end
end
end
end
if do_C2M
if BEST.FOM == -inf
param.Min_VEO_Test=0;
else
break
end
end
end
%%
if 0
fprintf('old loops = %d\n',old_loops);
fprintf('new loops = %d\n',new_loops);
display(sprintf('\n :loops = %g',pxi))
end
%turn this on to review if FOM changes sign more than once in an itick loop
if 0
DIR_CHANGE={};
for m=1:length(Gffe_values)
for n=1:length(gdc_values)
for k=1:lf_indx
FOM_this_mat=squeeze(FOM_TRACKER(m,n,k,:,:));
%x reveals if FOM on a particular row (locked txffe, moving itick) goes up or down
%1 = goes up, -1=goes down
x=sign(diff(FOM_this_mat')');
%y = change in sign on x. the location of a "2" is where FOM changes direction
y=abs(diff(x'))';
%the goal is the FOM only changes direction once. so count the occurences of the 2
for j=1:size(FOM_this_mat,1)
z{j}=find(y(j,:)==2);
end
zL=cellfun('length',z);
%return any row where FOM changed direction more than once
DIR_CHANGE{j,k}=find(zL>1);
end
end
end
multi_direction_change=find(~cellfun('isempty',DIR_CHANGE))
end
if 1
fprintf('\n');
fprintf('Itick cases = %d\n', itick_cases);
fprintf('Itick skips = %d\n', itick_skips);
if itick_cases > 0
fprintf('Itick skip rate = %.1f %%\n', ...
100*itick_skips/itick_cases);
end
fprintf('Edge hits = %d (%.2f%% of itick cases)\n', ...
edge_hit_count, ...
100*edge_hit_count/max(1,itick_cases));
end
%% Check if EQ failed (take last setting)
if isempty(BEST.cursor_i)
result.eq_failed=true;
fprintf('equalization failed\n');
BEST = OptFom_Update_Best_Settings_EQ_Failed(BEST, THIS, sbr, chdata, param, OP);
if do_C2M
return
end
else
result.eq_failed=false; % RIM 12/30/2023
end
f=1e8:1e8:100e9;
%use length of BEST.sbr for time axis in case zero padding was performed
length_sbr=length(BEST.sbr);
t=0:param.ui/param.samples_per_ui:(length_sbr-1)*param.ui/param.samples_per_ui;
%% Update fields in BEST that are only needed after EQ optimization concludes
BEST = OptFom_Update_BEST_Post_Optimize(BEST, f, param, OP);
%% Plot Results
OptFom_Plot_Best_Results(BEST, t, f, chdata, param, OP);
if OP.DISPLAY_WINDOW
close(hwaitbar);
else
fprintf('\n');
end
%% Create final output structure
result = OptFom_Create_Output(result, BEST, t, chdata, param, OP);
________________________________________________________________________
To unsubscribe from the STDS-802-3-COM list, click the following link: https://listserv.ieee.org/cgi-bin/wa?SUBED1=STDS-802-3-COM&A=1