/// <summary>
        /// Execute one iteration of time-granting loop.
        /// </summary>
        /// <remarks>
        /// The steps are as follows:
        /// (1) remove and forget all slave handles that requested detaching
        /// (2) check if there are any blocked slaves; if so DO NOT grant a time interval
        /// (2.1) if there are no blocked slaves grant a new time interval to every slave
        /// (3) wait for all slaves that are relevant in this execution (it can be either all slaves or just blocked ones) until they report back
        /// (4) (optional) sleep if the virtual time passed faster than a real one; this step is executed if <see cref="AdvanceImmediately"> is not set and <see cref="Performance"> is low enough
        /// (5) update elapsed virtual time
        /// (6) execute sync hook and delayed actions if any
        /// </remarks>
        /// <param name="virtualTimeElapsed">Contains the amount of virtual time that passed during execution of this method. It is the minimal value reported by a slave (i.e, some slaves can report higher/lower values).</param>
        /// <returns>
        /// True if sync point has just been reached or False if the execution has been blocked.
        /// </returns>
        protected bool InnerExecute(out TimeInterval virtualTimeElapsed)
        {
            DebugHelper.Assert(NearestSyncPoint.Ticks >= ElapsedVirtualTime.Ticks, "Nearest sync point set in the past");

            isBlocked = false;
            var quantum = NearestSyncPoint - ElapsedVirtualTime;

            this.Trace($"Starting a loop with #{quantum.Ticks} ticks");

            virtualTimeElapsed = TimeInterval.Empty;
            State = TimeSourceState.ReportingElapsedTime;
            using (sync.LowPriority)
            {
                if ((isPaused || !isStarted) && recentlyUnblockedSlaves.Count == 0)
                {
                    // the time source is not started and it has not acknowledged any unblocks - it means no one is currently working
                    DebugHelper.Assert(handles.All.All(x => !x.SourceSideActive), "No source side active slaves were expected at this point.");

                    State = TimeSourceState.Idle;
                    EnterBlockedState();
                    return(false);
                }

                handles.LatchAllAndCollectGarbage();
                var shouldGrantTime = handles.AreAllReadyForNewGrant;

                this.Trace($"Iteration start: slaves left {handles.ActiveCount}; will we try to grant time? {shouldGrantTime}");
                elapsedAtLastGrant = stopwatch.Elapsed;
                if (handles.ActiveCount > 0)
                {
                    if (shouldGrantTime && quantum != TimeInterval.Empty)
                    {
                        this.Trace($"Granting {quantum.Ticks} ticks");
                        // inform all slaves about elapsed time
                        foreach (var slave in handles)
                        {
                            slave.GrantTimeInterval(quantum);
                        }
                    }

                    // in case we did not grant any time due to quantum being empty, we must not call wait as well
                    if (!(shouldGrantTime && quantum == TimeInterval.Empty))
                    {
                        this.Trace("Waiting for slaves");
                        // wait for everyone to report back
                        State = TimeSourceState.WaitingForReportBack;
                        TimeInterval?minInterval = null;
                        foreach (var slave in handles.WithLinkedListNode)
                        {
                            var result = slave.Value.WaitUntilDone(out var usedInterval);
                            if (!result.IsDone)
                            {
                                EnterBlockedState();
                            }

                            handles.UpdateHandle(slave);

                            if (result.IsUnblockedRecently)
                            {
                                Antmicro.Renode.Debugging.DebugHelper.Assert(recentlyUnblockedSlaves.Contains(slave.Value), $"Expected slave to be in {nameof(recentlyUnblockedSlaves)} collection.");
                                recentlyUnblockedSlaves.Remove(slave.Value);
                                this.Trace($"Number of unblocked slaves set to {recentlyUnblockedSlaves.Count}");
                                if (recentlyUnblockedSlaves.Count == 0)
                                {
                                    sync.Pulse();
                                }
                            }
                            if (minInterval == null || minInterval > slave.Value.TotalElapsedTime)
                            {
                                minInterval = slave.Value.TotalElapsedTime;
                            }
                        }

                        if (minInterval != null)
                        {
                            virtualTimeElapsed = minInterval.Value - ElapsedVirtualTime;
                        }
                    }
                }
                else
                {
                    this.Trace($"There are no slaves, updating VTE by {quantum.Ticks}");
                    // if there are no slaves just make the time pass
                    virtualTimeElapsed = quantum;

                    TimePassed?.Invoke(quantum);
                }

                handles.UnlatchAll();

                State = TimeSourceState.Sleeping;
                var elapsedThisTime = stopwatch.Elapsed - elapsedAtLastGrant;
                if (!AdvanceImmediately)
                {
                    var scaledVirtualTicksElapsed = virtualTimeElapsed.WithScaledTicks(1 / Performance).ToTimeSpan() - elapsedThisTime;
                    sleeper.Sleep(scaledVirtualTicksElapsed);
                }

                lock (hostTicksElapsed)
                {
                    this.Trace($"Updating virtual time by {virtualTimeElapsed.InMicroseconds} us");
                    this.virtualTicksElapsed.Update(virtualTimeElapsed.InMicroseconds);
                    this.hostTicksElapsed.Update(elapsedThisTime.InMicroseconds());
                }
            }

            if (!isBlocked)
            {
                ExecuteSyncPhase();
            }
            else
            {
                BlockHook?.Invoke();
            }

            State = TimeSourceState.Idle;

            this.Trace($"The end of {nameof(InnerExecute)} with result={!isBlocked}");
            return(!isBlocked);
        }
Example #2
0
        /// <summary>
        /// Execute one iteration of time-granting loop.
        /// </summary>
        /// <remarks>
        /// The steps are as follows:
        /// (1) remove and forget all slave handles that requested detaching
        /// (2) check if there are any blocked slaves; if so DO NOT grant a time interval
        /// (2.1) if there are no blocked slaves grant a new time interval to every slave
        /// (3) wait for all slaves that are relevant in this execution (it can be either all slaves or just blocked ones) until they report back
        /// (4) (optional) sleep if the virtual time passed faster than a real one; this step is executed if <see cref="AdvanceImmediately"> is not set and <see cref="Performance"> is low enough
        /// (5) update elapsed virtual time
        /// (6) execute sync hook and delayed actions if any
        /// </remarks>
        /// <param name="virtualTimeElapsed">Contains the amount of virtual time that passed during execution of this method. It is the minimal value reported by a slave (i.e, some slaves can report higher/lower values).</param>
        /// <param name="timeLimit">Maximum amount of virtual time that can pass during the execution of this method. If not set, current <see cref="Quantum"> is used.</param>
        /// <returns>
        /// True if sync point has just been reached or False if the execution has been blocked.
        /// </returns>
        protected bool InnerExecute(out TimeInterval virtualTimeElapsed, TimeInterval?timeLimit = null)
        {
            if (updateNearestSyncPoint)
            {
                NearestSyncPoint      += timeLimit.HasValue ? TimeInterval.Min(timeLimit.Value, Quantum) : Quantum;
                updateNearestSyncPoint = false;
                this.Trace($"Updated NearestSyncPoint to: {NearestSyncPoint}");
            }
            DebugHelper.Assert(NearestSyncPoint.Ticks >= ElapsedVirtualTime.Ticks, $"Nearest sync point set in the past: EVT={ElapsedVirtualTime} NSP={NearestSyncPoint}");

            isBlocked = false;
            var quantum = NearestSyncPoint - ElapsedVirtualTime;

            this.Trace($"Starting a loop with #{quantum.Ticks} ticks");

            SynchronizeVirtualTime();
            var elapsedVirtualTimeAtStart = ElapsedVirtualTime;

            using (sync.LowPriority)
            {
                handles.LatchAllAndCollectGarbage();
                var shouldGrantTime = handles.AreAllReadyForNewGrant;

                this.Trace($"Iteration start: slaves left {handles.ActiveCount}; will we try to grant time? {shouldGrantTime}");

                if (handles.ActiveCount > 0)
                {
                    var executor = new PhaseExecutor <LinkedListNode <TimeHandle> >();

                    if (!shouldGrantTime)
                    {
                        executor.RegisterPhase(ExecuteUnblockPhase);
                        executor.RegisterPhase(ExecuteWaitPhase);
                    }
                    else if (quantum != TimeInterval.Empty)
                    {
                        executor.RegisterPhase(s => ExecuteGrantPhase(s, quantum));
                        executor.RegisterPhase(ExecuteWaitPhase);
                    }

                    if (ExecuteInSerial)
                    {
                        executor.ExecuteInSerial(handles.WithLinkedListNode);
                    }
                    else
                    {
                        executor.ExecuteInParallel(handles.WithLinkedListNode);
                    }

                    SynchronizeVirtualTime();
                    virtualTimeElapsed = ElapsedVirtualTime - elapsedVirtualTimeAtStart;
                }
                else
                {
                    this.Trace($"There are no slaves, updating VTE by {quantum.Ticks}");
                    // if there are no slaves just make the time pass
                    virtualTimeElapsed = quantum;

                    UpdateTime(quantum);
                    // here we must trigger `TimePassed` manually as no handles has been updated so they won't reflect the passed time
                    TimePassed?.Invoke(quantum);
                }

                handles.UnlatchAll();
            }

            SinksReportedkHook?.Invoke();
            if (!isBlocked)
            {
                ExecuteSyncPhase();
                updateNearestSyncPoint = true;
            }
            else
            {
                BlockHook?.Invoke();
            }

            State = TimeSourceState.Idle;

            this.Trace($"The end of {nameof(InnerExecute)} with result={!isBlocked}");
            return(!isBlocked);
        }
        /// <summary>
        /// Execute one iteration of time-granting loop.
        /// </summary>
        /// <remarks>
        /// The steps are as follows:
        /// (1) remove and forget all slave handles that requested detaching
        /// (2) check if there are any blocked slaves; if so DO NOT grant a time interval
        /// (2.1) if there are no blocked slaves grant a new time interval to every slave
        /// (3) wait for all slaves that are relevant in this execution (it can be either all slaves or just blocked ones) until they report back
        /// (4) (optional) sleep if the virtual time passed faster than a real one; this step is executed if <see cref="AdvanceImmediately"> is not set and <see cref="Performance"> is low enough
        /// (5) update elapsed virtual time
        /// (6) execute sync hook and delayed actions if any
        /// </remarks>
        /// <param name="virtualTimeElapsed">Contains the amount of virtual time that passed during execution of this method. It is the minimal value reported by a slave (i.e, some slaves can report higher/lower values).</param>
        /// <param name="timeLimit">Maximum amount of virtual time that can pass during the execution of this method. If not set, current <see cref="Quantum"> is used.</param>
        /// <returns>
        /// True if sync point has just been reached or False if the execution has been blocked.
        /// </returns>
        protected bool InnerExecute(out TimeInterval virtualTimeElapsed, TimeInterval?timeLimit = null)
        {
            if (updateNearestSyncPoint)
            {
                NearestSyncPoint      += timeLimit.HasValue ? TimeInterval.Min(timeLimit.Value, Quantum) : Quantum;
                updateNearestSyncPoint = false;
                this.Trace($"Updated NearestSyncPoint to: {NearestSyncPoint}");
            }
            DebugHelper.Assert(NearestSyncPoint.Ticks >= ElapsedVirtualTime.Ticks, $"Nearest sync point set in the past: EVT={ElapsedVirtualTime} NSP={NearestSyncPoint}");

            isBlocked = false;
            var quantum = NearestSyncPoint - ElapsedVirtualTime;

            this.Trace($"Starting a loop with #{quantum.Ticks} ticks");

            virtualTimeElapsed = TimeInterval.Empty;
            using (sync.LowPriority)
            {
                handles.LatchAllAndCollectGarbage();
                var shouldGrantTime = handles.AreAllReadyForNewGrant;

                this.Trace($"Iteration start: slaves left {handles.ActiveCount}; will we try to grant time? {shouldGrantTime}");
                elapsedAtLastGrant = stopwatch.Elapsed;

                if (handles.ActiveCount > 0)
                {
                    var executor = new PhaseExecutor <LinkedListNode <TimeHandle> >();

                    if (!shouldGrantTime)
                    {
                        executor.RegisterPhase(ExecuteUnblockPhase);
                        executor.RegisterPhase(ExecuteWaitPhase);
                    }
                    else if (quantum != TimeInterval.Empty)
                    {
                        executor.RegisterPhase(s => ExecuteGrantPhase(s, quantum));
                        executor.RegisterPhase(ExecuteWaitPhase);
                    }

                    if (ExecuteInSerial)
                    {
                        executor.ExecuteInSerial(handles.WithLinkedListNode);
                    }
                    else
                    {
                        executor.ExecuteInParallel(handles.WithLinkedListNode);
                    }

                    var commonElapsedTime = handles.CommonElapsedTime;
                    DebugHelper.Assert(commonElapsedTime >= ElapsedVirtualTime, $"A slave reports time from the past! The current virtual time is {ElapsedVirtualTime}, but {commonElapsedTime} has been reported");
                    virtualTimeElapsed = commonElapsedTime - ElapsedVirtualTime;
                }
                else
                {
                    this.Trace($"There are no slaves, updating VTE by {quantum.Ticks}");
                    // if there are no slaves just make the time pass
                    virtualTimeElapsed = quantum;

                    // here we must trigger `TimePassed` manually as no handles has been updated so they won't reflect the passed time
                    TimePassed?.Invoke(quantum);
                }

                handles.UnlatchAll();

                State = TimeSourceState.Sleeping;
                var elapsedThisTime = stopwatch.Elapsed - elapsedAtLastGrant;
                if (!AdvanceImmediately)
                {
                    var scaledVirtualTicksElapsed = virtualTimeElapsed.WithScaledTicks(1 / Performance).ToTimeSpan() - elapsedThisTime;
                    sleeper.Sleep(scaledVirtualTicksElapsed);
                }

                lock (hostTicksElapsed)
                {
                    this.Trace($"Updating virtual time by {virtualTimeElapsed.InMicroseconds} us");
                    this.virtualTicksElapsed.Update(virtualTimeElapsed.Ticks);
                    this.hostTicksElapsed.Update(TimeInterval.FromTimeSpan(elapsedThisTime).Ticks);
                }
            }

            if (!isBlocked)
            {
                ExecuteSyncPhase();
                updateNearestSyncPoint = true;
            }
            else
            {
                BlockHook?.Invoke();
            }

            State = TimeSourceState.Idle;

            this.Trace($"The end of {nameof(InnerExecute)} with result={!isBlocked}");
            return(!isBlocked);
        }