classdef OverheadProbeTarget < coder.profile.ExecutionTimeProbeTarget
    %OVERHEADPROBETARGET generates the benchmark for the overhead
    %estimation

%   Copyright 2018 The MathWorks, Inc.

    properties (Access = private)
       TaskEstimation = [];
       FcnEstimation = [];
       FreezeEstimation = [];
       GenericEstimation = [];
    end
    
    methods
        
        function this = OverheadProbeTarget (lStartFnc, lEndFnc)
            this = this@coder.profile.ExecutionTimeProbeTarget(lStartFnc, lEndFnc, ...
                coder.profile.ExecutionTimeProbeTarget.ExecProbe);
            lTaskPostfix = coder.profile.OverheadProbeComponentRegistry.TaskPostfix;
            this.TaskEstimation = {[lStartFnc lTaskPostfix], ...
                                   [lEndFnc lTaskPostfix]};
            lFunctionPostfix = coder.profile.OverheadProbeComponentRegistry.FunctionPostfix;
            this.FcnEstimation = {[lStartFnc lFunctionPostfix], ...
                                  [lEndFnc lFunctionPostfix]};
            lGenericPostfix = coder.profile.OverheadProbeComponentRegistry.GenericPostfix;
            this.GenericEstimation = {[lStartFnc lGenericPostfix], ...
                                      [lEndFnc lGenericPostfix]};
            lFreezePostfix = coder.profile.OverheadProbeComponentRegistry.FreezePostfix;
            this.FreezeEstimation = {[lStartFnc lFreezePostfix], ...
                                     [lEndFnc lFreezePostfix]};
        end
        
        function targetWriteProbeExportedFcnPrototypes(this, writer, ~)
            writer.wComment('overhead estimation');
            lType = this.IdentifierCDataType;
            writer.writeLine('void xilProfilingEstimateOverhead(%s, %s, %s, %s, %s, %s);', lType, lType, lType, lType, lType, lType);
        end
        
        function targetWriteSectionFcns(this, writer, targetCollectDataFcnName, ~)
            lTimerType = this.ProfilingTimer.getTimerCType;

            writer.wComment(['This variable contains the value to upload when ' ...
                             this.GenericEstimation{1}, ' and ', ...
                             this.GenericEstimation{2}, ' are called']);
            writer.writeLine('static %s xsd_xil_timer_overhead_generic = 0;', lTimerType);
            writer.newLine;
            
            this.writeSectionProfilingStartEnd(true,  this.TaskProbe, writer, ...
                this.TaskEstimation{1}, this.TaskEstimation{2});
            this.writeSectionProfilingStartEnd(false, this.TaskProbe, writer, ...
                this.TaskEstimation{1}, this.TaskEstimation{2});
            
            this.writeSectionProfilingStartEnd(true,  this.ExecProbe, writer, ...
                this.FcnEstimation{1}, this.FcnEstimation{2});
            this.writeSectionProfilingStartEnd(false, this.ExecProbe, writer, ...
                this.FcnEstimation{1}, this.FcnEstimation{2});
            
            this.writeSectionProfilingStartEnd(true,  this.FilterProbe, writer, ...
                this.FreezeEstimation{1}, this.FreezeEstimation{2});
            this.writeSectionProfilingStartEnd(false, this.FilterProbe, writer, ...
                this.FreezeEstimation{1}, this.FreezeEstimation{2});
            
            this.targetWriteBenchmarkDefinition(writer);
            
            this.targetWriteGenericUpload(writer, true, ...
                this.GenericEstimation{1}, targetCollectDataFcnName);
            this.targetWriteGenericUpload(writer, false, ...
                this.GenericEstimation{2}, targetCollectDataFcnName)
        end
    end
    
    methods (Access = private)
        
        function targetWriteGenericUpload(this, writer, isStart, fcnName, targetCollectDataFcnName)
            
            lIdType = this.IdentifierCDataType;
            lIdInput = 'sectionId';
            
            writer.wBlockStart('void %s(%s %s)', fcnName, lIdType, lIdInput);
            lIdToUpload = lIdInput;
            if ~isStart
                lIdToUpload = [lIdInput 'Neg'];
                writer.writeLine('%s %s = ~%s;', lIdType, lIdToUpload, lIdInput);
            end
            writer.writeLine('%s((void *)(&xsd_xil_timer_overhead_generic), (%s)(SIZEOF_TIMER_TYPE), %s);', ...
                targetCollectDataFcnName, lIdType, lIdToUpload);
            writer.wBlockEnd;
        end
        
        function targetWriteBenchmarkDefinition(this, writer)
            lIdType = this.IdentifierCDataType;
            lTimerType = this.ProfilingTimer.getTimerCType;
            timerFunc =  this.ProfilingTimer.getReadTimerExpression;
            
            writer.wBlockStart('void xilProfilingEstimateOverhead(%s sIDTimer, %s sIDFreezing, %s sIDTask, %s sIDFcn, %s sIDDummy, %s numIters)', ...
                lIdType, lIdType, lIdType, lIdType, lIdType, lIdType);
            
            % define local variables
            writer.writeLine('%s elementIdx;', lIdType);
            writer.writeLine('%s t_diff;', lTimerType);
            writer.writeLine('%s t1;', lTimerType);
            writer.writeLine('%s t2;', lTimerType);
            writer.wComment('The overhead to read the timestamp is used only locally');
            writer.writeLine('%s timestamp_overhead = 0;', lTimerType);
            
            % reset global variables
            writer.wComment('Reset global variables');
            writer.writeLine('xsd_xil_timer_overhead_total = 0;');
            writer.writeLine('xsd_xil_timer_overhead_generic = 0;');

            % if the variables are already set, upload an instance of them 
            % and use them. Otherwise, they are already zero, no need to
            % reset
            writer.wComment('If the overhead variables are already set, use them directly');
            writer.wBlockStart(['if ((xsd_xil_timer_overhead_freezing != 0) || ' ...
                                    '(xsd_xil_timer_overhead_task != 0) || ' ...
                                    '(xsd_xil_timer_overhead_function != 0))']);
            writer.writeLine('return;');
            writer.wBlockEnd;

            % timer overhead estimation
            writer.wComment('Compute the overhead to read the timestamp twice');
            writer.wBlockStart('for (elementIdx=0; elementIdx<numIters; elementIdx=elementIdx+1)');
            % - get overhead
            writer.writeLine('t1 = %s;', timerFunc);
            writer.writeLine('t2 = %s;', timerFunc);
            % - upload overhead
            writer.writeLine('%s_estimate(sIDTimer);', this.GenericEstimation{1});
            writer.writeLine('xsd_xil_timer_overhead_generic = xsd_xil_timer_overhead_generic + (t2 - t1);');
            writer.writeLine('%s_estimate(sIDTimer);', this.GenericEstimation{2});
            writer.wBlockEnd;
            % - set overhead
            writer.writeLine('timestamp_overhead = xsd_xil_timer_overhead_generic / numIters;');

            % overhead estimation to freeze/unfreeze timer
            writer.wComment('Compute the overhead to freeze/unfreeze the timer');
            writer.writeLine('xsd_xil_timer_overhead_generic = 0;');
            writer.wBlockStart('for (elementIdx=0; elementIdx<numIters; elementIdx=elementIdx+1)');
            % - get overhead
            writer.writeLine('xsd_xil_timer_corrected = 0;');
            writer.writeLine('xsd_xil_timer_unfreeze = %s;', timerFunc);
            writer.writeLine('%s_estimate(sIDDummy);', this.FreezeEstimation{1});
            writer.writeLine('%s_estimate(sIDDummy);', this.FreezeEstimation{2});
            writer.writeLine('t2 = %s;', timerFunc);
            writer.writeLine('t_diff = (t2 - xsd_xil_timer_unfreeze) + xsd_xil_timer_corrected;');
            writer.wBlockStart('if (t_diff > timestamp_overhead)');
            writer.writeLine('t_diff = t_diff - timestamp_overhead;');
            writer.wBlockMiddle('else');
            writer.writeLine('t_diff = 0;');
            writer.wBlockEnd;
            % - upload overhead
            writer.writeLine('%s_estimate(sIDFreezing);', this.GenericEstimation{1});
            writer.writeLine('xsd_xil_timer_overhead_generic = xsd_xil_timer_overhead_generic + t_diff;');
            writer.writeLine('%s_estimate(sIDFreezing);', this.GenericEstimation{2});
            writer.wBlockEnd;
            % - set overhead
            writer.writeLine('xsd_xil_timer_overhead_freezing = xsd_xil_timer_overhead_generic / numIters;');
            % - reset global variables
            writer.writeLine('xsd_xil_timer_corrected = 0;');

            % task start/end estimation
            writer.wComment('Compute the overhead to call TaskTimeStart/TaskTimeEnd');
            writer.wBlockStart('for (elementIdx=0; elementIdx<numIters; elementIdx++)');
            % - get/upload overhead
            writer.writeLine('%s_estimate(sIDTask);', this.TaskEstimation{1});
            writer.writeLine('%s_estimate(sIDTask);', this.TaskEstimation{2});
            writer.wBlockEnd;
            % - set overhead
            writer.writeLine('xsd_xil_timer_overhead_task = xsd_xil_timer_corrected / numIters;');
            
            % function start/end estimation
            writer.wComment('Compute the overhead to call ProfileStart/ProfileEnd');
            writer.writeLine('xsd_xil_timer_overhead_generic = 0;');
            writer.wBlockStart('for (elementIdx=0; elementIdx<numIters; elementIdx++)');
            % - get/upload overhead
            writer.writeLine('xsd_xil_timer_corrected = 0;');
            writer.writeLine('xsd_xil_timer_unfreeze = %s;', timerFunc);
            writer.writeLine('%s_estimate(sIDDummy);', this.FcnEstimation{1});
            writer.writeLine('%s_estimate(sIDDummy);', this.FcnEstimation{2});
            writer.writeLine('t2 = %s;', timerFunc);
            writer.writeLine('t_diff = (t2 - xsd_xil_timer_unfreeze) + xsd_xil_timer_corrected;');
            writer.wBlockStart('if (t_diff > timestamp_overhead)');
            writer.writeLine('t_diff = t_diff - timestamp_overhead;');
            writer.wBlockMiddle('else');
            writer.writeLine('t_diff = 0;');
            writer.wBlockEnd;
            writer.writeLine('%s_estimate(sIDFcn);', this.GenericEstimation{1});
            writer.writeLine('xsd_xil_timer_overhead_generic = xsd_xil_timer_overhead_generic + t_diff;');
            writer.writeLine('%s_estimate(sIDFcn);', this.GenericEstimation{2});
            writer.wBlockEnd;
            % - set overhead
            writer.writeLine('xsd_xil_timer_overhead_function = xsd_xil_timer_overhead_generic / numIters;');
            % - reset global variables
            writer.writeLine('xsd_xil_timer_overhead_generic = 0;');
            writer.writeLine('xsd_xil_timer_corrected = 0;');
            writer.writeLine('xsd_xil_timer_unfreeze = 0;');
            writer.writeLine('xsd_xil_timer_overhead_total = 0;');
            
            writer.wBlockEnd;
        end 
    end
end


