scheduler.hh revision 13901
1/* 2 * Copyright 2018 Google, Inc. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: redistributions of source code must retain the above copyright 7 * notice, this list of conditions and the following disclaimer; 8 * redistributions in binary form must reproduce the above copyright 9 * notice, this list of conditions and the following disclaimer in the 10 * documentation and/or other materials provided with the distribution; 11 * neither the name of the copyright holders nor the names of its 12 * contributors may be used to endorse or promote products derived from 13 * this software without specific prior written permission. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 * 27 * Authors: Gabe Black 28 */ 29 30#ifndef __SYSTEMC_CORE_SCHEDULER_HH__ 31#define __SYSTEMC_CORE_SCHEDULER_HH__ 32 33#include <functional> 34#include <map> 35#include <mutex> 36#include <set> 37#include <vector> 38 39#include "base/logging.hh" 40#include "sim/core.hh" 41#include "sim/eventq.hh" 42#include "systemc/core/channel.hh" 43#include "systemc/core/list.hh" 44#include "systemc/core/process.hh" 45#include "systemc/core/sched_event.hh" 46 47class Fiber; 48 49namespace sc_gem5 50{ 51 52class TraceFile; 53 54typedef NodeList<Process> ProcessList; 55typedef NodeList<Channel> ChannelList; 56 57/* 58 * The scheduler supports three different mechanisms, the initialization phase, 59 * delta cycles, and timed notifications. 60 * 61 * INITIALIZATION PHASE 62 * 63 * The initialization phase has three parts: 64 * 1. Run requested channel updates. 65 * 2. Make processes which need to initialize runnable (methods and threads 66 * which didn't have dont_initialize called on them). 67 * 3. Process delta notifications. 68 * 69 * First, the Kernel SimObject calls the update() method during its startup() 70 * callback which handles the requested channel updates. The Kernel also 71 * schedules an event to be run at time 0 with a slightly elevated priority 72 * so that it happens before any "normal" event. 73 * 74 * When that t0 event happens, it calls the schedulers prepareForInit method 75 * which performs step 2 above. That indirectly causes the scheduler's 76 * readyEvent to be scheduled with slightly lowered priority, ensuring it 77 * happens after any "normal" event. 78 * 79 * Because delta notifications are scheduled at the standard priority, all 80 * of those events will happen next, performing step 3 above. Once they finish, 81 * if the readyEvent was scheduled above, there shouldn't be any higher 82 * priority events in front of it. When it runs, it will start the first 83 * evaluate phase of the first delta cycle. 84 * 85 * DELTA CYCLE 86 * 87 * A delta cycle has three phases within it. 88 * 1. The evaluate phase where runnable processes are allowed to run. 89 * 2. The update phase where requested channel updates hapen. 90 * 3. The delta notification phase where delta notifications happen. 91 * 92 * The readyEvent runs all three steps of the delta cycle. It first goes 93 * through the list of runnable processes and executes them until the set is 94 * empty, and then immediately runs the update phase. Since these are all part 95 * of the same event, there's no chance for other events to intervene and 96 * break the required order above. 97 * 98 * During the update phase above, the spec forbids any action which would make 99 * a process runnable. That means that once the update phase finishes, the set 100 * of runnable processes will be empty. There may, however, have been some 101 * delta notifications/timeouts which will have been scheduled during either 102 * the evaluate or update phase above. Those will have been accumulated in the 103 * scheduler, and are now all executed. 104 * 105 * If any processes became runnable during the delta notification phase, the 106 * readyEvent will have been scheduled and will be waiting and ready to run 107 * again, effectively starting the next delta cycle. 108 * 109 * TIMED NOTIFICATION PHASE 110 * 111 * If no processes became runnable, the event queue will continue to process 112 * events until it comes across an event which represents all the timed 113 * notifications which are supposed to happen at a particular time. The object 114 * which tracks them will execute all those notifications, and then destroy 115 * itself. If the readyEvent is now ready to run, the next delta cycle will 116 * start. 117 * 118 * PAUSE/STOP 119 * 120 * To inject a pause from sc_pause which should happen after the current delta 121 * cycle's delta notification phase, an event is scheduled with a lower than 122 * normal priority, but higher than the readyEvent. That ensures that any 123 * delta notifications which are scheduled with normal priority will happen 124 * first, since those are part of the current delta cycle. Then the pause 125 * event will happen before the next readyEvent which would start the next 126 * delta cycle. All of these events are scheduled for the current time, and so 127 * would happen before any timed notifications went off. 128 * 129 * To inject a stop from sc_stop, the delta cycles should stop before even the 130 * delta notifications have happened, but after the evaluate and update phases. 131 * For that, a stop event with slightly higher than normal priority will be 132 * scheduled so that it happens before any of the delta notification events 133 * which are at normal priority. 134 * 135 * MAX RUN TIME 136 * 137 * When sc_start is called, it's possible to pass in a maximum time the 138 * simulation should run to, at which point sc_pause is implicitly called. The 139 * simulation is supposed to run up to the latest timed notification phase 140 * which is less than or equal to the maximum time. In other words it should 141 * run timed notifications at the maximum time, but not the subsequent evaluate 142 * phase. That's implemented by scheduling an event at the max time with a 143 * priority which is lower than all the others except the ready event. Timed 144 * notifications will happen before it fires, but it will override any ready 145 * event and prevent the evaluate phase from starting. 146 */ 147 148class Scheduler 149{ 150 public: 151 typedef std::list<ScEvent *> ScEvents; 152 153 class TimeSlot : public ::Event 154 { 155 public: 156 TimeSlot() : ::Event(Default_Pri, AutoDelete) {} 157 158 ScEvents events; 159 void process(); 160 }; 161 162 typedef std::map<Tick, TimeSlot *> TimeSlots; 163 164 Scheduler(); 165 ~Scheduler(); 166 167 void clear(); 168 169 const std::string name() const { return "systemc_scheduler"; } 170 171 uint64_t numCycles() { return _numCycles; } 172 Process *current() { return _current; } 173 174 void initPhase(); 175 176 // Register a process with the scheduler. 177 void reg(Process *p); 178 179 // Run the next process, if there is one. 180 void yield(); 181 182 // Put a process on the ready list. 183 void ready(Process *p); 184 185 // Mark a process as ready if init is finished, or put it on the list of 186 // processes to be initialized. 187 void resume(Process *p); 188 189 // Remove a process from the ready/init list if it was on one of them, and 190 // return if it was. 191 bool suspend(Process *p); 192 193 // Schedule an update for a given channel. 194 void requestUpdate(Channel *c); 195 // Same as above, but may be called from a different thread. 196 void asyncRequestUpdate(Channel *c); 197 198 // Run the given process immediately, preempting whatever may be running. 199 void 200 runNow(Process *p) 201 { 202 // This function may put a process on the wrong list, ie a thread 203 // the method list. That's fine since that's just a performance 204 // optimization, and the important thing here is how the processes are 205 // ordered. 206 207 // If a process is running, schedule it/us to run again. 208 if (_current) 209 readyListMethods.pushFirst(_current); 210 // Schedule p to run first. 211 readyListMethods.pushFirst(p); 212 yield(); 213 } 214 215 // Run this process at the next opportunity. 216 void 217 runNext(Process *p) 218 { 219 // Like above, it's ok if this isn't a method. Putting it on this list 220 // just gives it priority. 221 readyListMethods.pushFirst(p); 222 if (!inEvaluate()) 223 scheduleReadyEvent(); 224 } 225 226 // Set an event queue for scheduling events. 227 void setEventQueue(EventQueue *_eq) { eq = _eq; } 228 229 // Get the current time according to gem5. 230 Tick getCurTick() { return eq ? eq->getCurTick() : 0; } 231 232 Tick 233 delayed(const ::sc_core::sc_time &delay) 234 { 235 return getCurTick() + delay.value(); 236 } 237 238 // For scheduling delayed/timed notifications/timeouts. 239 void 240 schedule(ScEvent *event, const ::sc_core::sc_time &delay) 241 { 242 Tick tick = delayed(delay); 243 if (tick < getCurTick()) 244 tick = getCurTick(); 245 246 // Delta notification/timeout. 247 if (delay.value() == 0) { 248 event->schedule(deltas, tick); 249 if (!inEvaluate() && !inUpdate()) 250 scheduleReadyEvent(); 251 return; 252 } 253 254 // Timed notification/timeout. 255 TimeSlot *&ts = timeSlots[tick]; 256 if (!ts) { 257 ts = new TimeSlot; 258 schedule(ts, tick); 259 } 260 event->schedule(ts->events, tick); 261 } 262 263 // For descheduling delayed/timed notifications/timeouts. 264 void 265 deschedule(ScEvent *event) 266 { 267 ScEvents *on = event->scheduledOn(); 268 269 if (on == &deltas) { 270 event->deschedule(); 271 return; 272 } 273 274 // Timed notification/timeout. 275 auto tsit = timeSlots.find(event->when()); 276 panic_if(tsit == timeSlots.end(), 277 "Descheduling event at time with no events."); 278 TimeSlot *ts = tsit->second; 279 ScEvents &events = ts->events; 280 assert(on == &events); 281 event->deschedule(); 282 283 // If no more events are happening at this time slot, get rid of it. 284 if (events.empty()) { 285 deschedule(ts); 286 timeSlots.erase(tsit); 287 } 288 } 289 290 void 291 completeTimeSlot(TimeSlot *ts) 292 { 293 assert(ts == timeSlots.begin()->second); 294 timeSlots.erase(timeSlots.begin()); 295 if (!runToTime && starved()) 296 scheduleStarvationEvent(); 297 scheduleTimeAdvancesEvent(); 298 } 299 300 // Pending activity ignores gem5 activity, much like how a systemc 301 // simulation wouldn't know about asynchronous external events (socket IO 302 // for instance) that might happen before time advances in a pure 303 // systemc simulation. Also the spec lists what specific types of pending 304 // activity needs to be counted, which obviously doesn't include gem5 305 // events. 306 307 // Return whether there's pending systemc activity at this time. 308 bool 309 pendingCurr() 310 { 311 return !readyListMethods.empty() || !readyListThreads.empty() || 312 !updateList.empty() || !deltas.empty(); 313 } 314 315 // Return whether there are pending timed notifications or timeouts. 316 bool 317 pendingFuture() 318 { 319 return !timeSlots.empty(); 320 } 321 322 // Return how many ticks there are until the first pending event, if any. 323 Tick 324 timeToPending() 325 { 326 if (pendingCurr()) 327 return 0; 328 if (pendingFuture()) 329 return timeSlots.begin()->first - getCurTick(); 330 return MaxTick - getCurTick(); 331 } 332 333 // Run scheduled channel updates. 334 void runUpdate(); 335 336 // Run delta events. 337 void runDelta(); 338 339 void start(Tick max_tick, bool run_to_time); 340 void oneCycle(); 341 342 void schedulePause(); 343 void scheduleStop(bool finish_delta); 344 345 enum Status 346 { 347 StatusOther = 0, 348 StatusEvaluate, 349 StatusUpdate, 350 StatusDelta, 351 StatusTiming, 352 StatusPaused, 353 StatusStopped 354 }; 355 356 bool elaborationDone() { return _elaborationDone; } 357 void elaborationDone(bool b) { _elaborationDone = b; } 358 359 bool paused() { return status() == StatusPaused; } 360 bool stopped() { return status() == StatusStopped; } 361 bool inEvaluate() { return status() == StatusEvaluate; } 362 bool inUpdate() { return status() == StatusUpdate; } 363 bool inDelta() { return status() == StatusDelta; } 364 bool inTiming() { return status() == StatusTiming; } 365 366 uint64_t changeStamp() { return _changeStamp; } 367 void stepChangeStamp() { _changeStamp++; } 368 369 // Throw upwards, either to sc_main or to the report handler if sc_main 370 // isn't running. 371 void throwUp(); 372 373 Status status() { return _status; } 374 void status(Status s) { _status = s; } 375 376 void registerTraceFile(TraceFile *tf) { traceFiles.insert(tf); } 377 void unregisterTraceFile(TraceFile *tf) { traceFiles.erase(tf); } 378 379 private: 380 typedef const EventBase::Priority Priority; 381 static Priority DefaultPriority = EventBase::Default_Pri; 382 383 static Priority StopPriority = DefaultPriority - 1; 384 static Priority PausePriority = DefaultPriority + 1; 385 static Priority MaxTickPriority = DefaultPriority + 2; 386 static Priority ReadyPriority = DefaultPriority + 3; 387 static Priority StarvationPriority = ReadyPriority; 388 static Priority TimeAdvancesPriority = EventBase::Maximum_Pri; 389 390 EventQueue *eq; 391 392 // For gem5 style events. 393 void 394 schedule(::Event *event, Tick tick) 395 { 396 if (initDone) 397 eq->schedule(event, tick); 398 else 399 eventsToSchedule[event] = tick; 400 } 401 402 void schedule(::Event *event) { schedule(event, getCurTick()); } 403 404 void 405 deschedule(::Event *event) 406 { 407 if (initDone) 408 eq->deschedule(event); 409 else 410 eventsToSchedule.erase(event); 411 } 412 413 ScEvents deltas; 414 TimeSlots timeSlots; 415 416 Process * 417 getNextReady() 418 { 419 Process *p = readyListMethods.getNext(); 420 return p ? p : readyListThreads.getNext(); 421 } 422 423 void runReady(); 424 EventWrapper<Scheduler, &Scheduler::runReady> readyEvent; 425 void scheduleReadyEvent(); 426 427 void pause(); 428 void stop(); 429 EventWrapper<Scheduler, &Scheduler::pause> pauseEvent; 430 EventWrapper<Scheduler, &Scheduler::stop> stopEvent; 431 432 const ::sc_core::sc_report *_throwUp; 433 434 bool 435 starved() 436 { 437 return (readyListMethods.empty() && readyListThreads.empty() && 438 updateList.empty() && deltas.empty() && 439 (timeSlots.empty() || timeSlots.begin()->first > maxTick) && 440 initList.empty()); 441 } 442 EventWrapper<Scheduler, &Scheduler::pause> starvationEvent; 443 void scheduleStarvationEvent(); 444 445 bool _elaborationDone; 446 bool _started; 447 bool _stopNow; 448 449 Status _status; 450 451 Tick maxTick; 452 Tick lastReadyTick; 453 void 454 maxTickFunc() 455 { 456 if (lastReadyTick != getCurTick()) 457 _changeStamp++; 458 pause(); 459 } 460 EventWrapper<Scheduler, &Scheduler::maxTickFunc> maxTickEvent; 461 462 void timeAdvances() { trace(false); } 463 EventWrapper<Scheduler, &Scheduler::timeAdvances> timeAdvancesEvent; 464 void 465 scheduleTimeAdvancesEvent() 466 { 467 if (!traceFiles.empty() && !timeAdvancesEvent.scheduled()) 468 schedule(&timeAdvancesEvent); 469 } 470 471 uint64_t _numCycles; 472 uint64_t _changeStamp; 473 474 Process *_current; 475 476 bool initDone; 477 bool runToTime; 478 bool runOnce; 479 480 ProcessList initList; 481 482 ProcessList readyListMethods; 483 ProcessList readyListThreads; 484 485 ChannelList updateList; 486 487 ChannelList asyncUpdateList; 488 std::mutex asyncListMutex; 489 490 std::map<::Event *, Tick> eventsToSchedule; 491 492 std::set<TraceFile *> traceFiles; 493 494 void trace(bool delta); 495}; 496 497extern Scheduler scheduler; 498 499// A proxy function to avoid having to expose the scheduler in header files. 500Process *getCurrentProcess(); 501 502inline void 503Scheduler::TimeSlot::process() 504{ 505 scheduler.stepChangeStamp(); 506 scheduler.status(StatusTiming); 507 508 try { 509 while (!events.empty()) 510 events.front()->run(); 511 } catch (...) { 512 if (events.empty()) 513 scheduler.completeTimeSlot(this); 514 else 515 scheduler.schedule(this); 516 scheduler.throwUp(); 517 } 518 519 scheduler.status(StatusOther); 520 scheduler.completeTimeSlot(this); 521} 522 523const ::sc_core::sc_report reportifyException(); 524 525} // namespace sc_gem5 526 527#endif // __SYSTEMC_CORE_SCHEDULER_H__ 528