diff --git a/lib/src/simulator.dart b/lib/src/simulator.dart index 78dbd3e9d..82bdd791f 100644 --- a/lib/src/simulator.dart +++ b/lib/src/simulator.dart @@ -76,8 +76,11 @@ abstract class Simulator { _pendingTimestamps.isNotEmpty || _injectedActions.isNotEmpty; /// Sorted storage for pending functions to execute at appropriate times. - static final SplayTreeMap> _pendingTimestamps = - SplayTreeMap>(); + static final SplayTreeMap> + _pendingTimestamps = SplayTreeMap>(); + + /// The list of actions to be performed in this timestamp + static ListQueue _pendingList = ListQueue(); /// Functions to be executed as soon as possible by the [Simulator]. /// @@ -192,7 +195,7 @@ abstract class Simulator { ' Current time is ${Simulator.time}'); } if (!_pendingTimestamps.containsKey(timestamp)) { - _pendingTimestamps[timestamp] = []; + _pendingTimestamps[timestamp] = ListQueue(); } _pendingTimestamps[timestamp]!.add(action); } @@ -246,58 +249,99 @@ abstract class Simulator { /// If there are no timestamps pending to execute, nothing will execute. static Future tick() async { if (_injectedActions.isNotEmpty) { - // injected actions will automatically be executed during tickExecute - await tickExecute(() {}); + // case 1 : ( the usual Rohd case ) + // The previous delta cycle did NOT do + // 'registerAction( _currentTimeStamp );'. + // In that case, _pendingTimestamps[_currentTimestamp] is null so we will + // add a new empty list, which will trigger a new delta cycle. + // + // case 2 : + // The previous delta cycle DID do 'registerAction( _currentTimestamp );'. + // In that case, there is *already* another tick scheduled for + // _currentTimestamp, and the injected actions will get called in + // the normal way. + // + // Either way, the end result is that a whole new tick gets scheduled for + // _currentTimestamp and any outstanding injected actions get executed. + + // ignore: unnecessary_lambdas + _pendingTimestamps.putIfAbsent(_currentTimestamp, () => ListQueue()); + } - // don't continue through the tick for injected actions, come back around - return; + // the main event loop + if (_updateTimeStamp()) { + _preTick(); + await _mainTick(); + _clkStable(); + await _outOfTick(); } + } + /// Updates [_currentTimestamp] with the next time stamp. + /// + /// Returns true iff there is a next time stamp. + /// + /// Also updates [_pendingList] with the list of actions scheduled for this + /// timestamp. + /// + /// If any of the actions in [_pendingList] schedule an action for + /// [_currentTimestamp], then this action is registered in the next delta + /// cycle. The next delta cycle is modelled as a new list of actions with the + /// same time as [_currentTimestamp]. + static bool _updateTimeStamp() { final nextTimeStamp = _pendingTimestamps.firstKey(); if (nextTimeStamp == null) { - return; + return false; } _currentTimestamp = nextTimeStamp; - final pendingList = _pendingTimestamps[nextTimeStamp]!; - _pendingTimestamps.remove(_currentTimestamp); - - await tickExecute(() async { - for (final func in pendingList) { - await func(); - } - }); - } - - /// Executes all pending injected actions. - static Future _executeInjectedActions() async { - while (_injectedActions.isNotEmpty) { - final injectedFunction = _injectedActions.removeFirst(); - await injectedFunction(); - } + // remove current list of actions but keep it for use in the mainTick phase + _pendingList = _pendingTimestamps.remove(_currentTimestamp)!; + return true; } - /// Performs the actual execution of a collection of actions for a [tick()]. - static Future tickExecute(dynamic Function() toExecute) async { + /// Executes the preTick phase. + static void _preTick() { _phase = SimulatorPhase.beforeTick; - - // useful for flop sampling _preTickController.add(null); + } + /// Executes the mainTick phase. + /// + /// After [_startTickController] is notified, this method awaits all the + /// actions registered with this tick, removing the action from [_pendingList] + /// as it goes. + static Future _mainTick() async { _phase = SimulatorPhase.mainTick; // useful for things that need to trigger every tick without other input _startTickController.add(null); - await toExecute(); + // execute the actions for this timestamp + while (_pendingList.isNotEmpty) { + await _pendingList.removeFirst()(); + } + } + + /// Executes the clkStable phase + static void _clkStable() { _phase = SimulatorPhase.clkStable; // useful for flop clk input stability _clkStableController.add(null); + } - await _executeInjectedActions(); + /// Executes the outOfTick phase + //// + /// Just before we end the current tick, we execute the injected actions, + /// removing them from [_injectedActions] as we go. + static Future _outOfTick() async { + while (_injectedActions.isNotEmpty) { + final injectedFunction = _injectedActions.removeFirst(); + await injectedFunction(); + } _phase = SimulatorPhase.outOfTick;