systemc: Implement sc_pause, sc_stop, and re-sc_start-ing.
authorGabe Black <gabeblack@google.com>
Tue, 17 Jul 2018 07:09:59 +0000 (00:09 -0700)
committerGabe Black <gabeblack@google.com>
Wed, 5 Sep 2018 06:08:22 +0000 (06:08 +0000)
This change further modifies the scheduler to implement the sc_pause
and sc_stop functions, and to ensure that calling sc_start again works.
Also, some small changes were made to how processes and contexts are
hooked up. Now, rather than checking whether a process is running to
determine wether it started on its own or needs to be started manually,
there's a bool which explicitly tracks whether it needs this step. The
problem was that once a thread finished, it wasn't considered running
any more. In that case it had run but finished, but that was
indistinguishable from it needing to run but not having been started.

Change-Id: I3aefb5493f91d9efa1a1382586196339b67925fe
Reviewed-on: https://gem5-review.googlesource.com/12031
Reviewed-by: Gabe Black <gabeblack@google.com>
Maintainer: Gabe Black <gabeblack@google.com>

src/systemc/core/process.cc
src/systemc/core/process.hh
src/systemc/core/process_types.hh
src/systemc/core/sc_main.cc
src/systemc/core/scheduler.cc
src/systemc/core/scheduler.hh

index 63d2ff94b7e1337ec2a050daa5d0803650914dc4..7ed187fe1c9034cc8b9561ab14a6387c85828ab7 100644 (file)
@@ -277,7 +277,6 @@ Process::finalize()
 void
 Process::run()
 {
-    _running = true;
     bool reset;
     do {
         reset = false;
@@ -287,7 +286,7 @@ Process::run()
             reset = exc.is_reset();
         }
     } while (reset);
-    _running = false;
+    _terminated = true;
 }
 
 void
@@ -323,9 +322,10 @@ Process::ready()
         scheduler.ready(this);
 }
 
-Process::Process(const char *name, ProcessFuncWrapper *func, bool _dynamic) :
+Process::Process(const char *name, ProcessFuncWrapper *func,
+        bool _dynamic, bool needs_start) :
     ::sc_core::sc_object(name), excWrapper(nullptr), func(func),
-    _running(false), _dynamic(_dynamic), _isUnwinding(false),
+    _needsStart(needs_start), _dynamic(_dynamic), _isUnwinding(false),
     _terminated(false), _suspended(false), _disabled(false),
     _syncReset(false), refCount(0), stackSize(::Fiber::DefaultStackSize),
     dynamicSensitivity(nullptr)
index 579ea602f53ce8d7a710f1fe5673d7b988468547..7c75d6244cec0727674d61dbbe5fc1177b3cb102 100644 (file)
@@ -246,7 +246,7 @@ class Process : public ::sc_core::sc_object, public ListNode
 {
   public:
     virtual ::sc_core::sc_curr_proc_kind procKind() const = 0;
-    bool running() const { return _running; }
+    bool needsStart() const { return _needsStart; }
     bool dynamic() const { return _dynamic; }
     bool isUnwinding() const { return _isUnwinding; }
     bool terminated() const { return _terminated; }
@@ -298,7 +298,8 @@ class Process : public ::sc_core::sc_object, public ListNode
     static Process *newest() { return _newest; }
 
   protected:
-    Process(const char *name, ProcessFuncWrapper *func, bool _dynamic);
+    Process(const char *name, ProcessFuncWrapper *func, bool _dynamic,
+            bool needs_start);
 
     static Process *_newest;
 
@@ -314,7 +315,7 @@ class Process : public ::sc_core::sc_object, public ListNode
 
     ProcessFuncWrapper *func;
     sc_core::sc_curr_proc_kind _procKind;
-    bool _running;
+    bool _needsStart;
     bool _dynamic;
     bool _isUnwinding;
     bool _terminated;
index 6d13592b6368dbcc7048a219aa64ab34bcba8e24..369fa726e94a72c56615a56d3948fba5886b9164 100644 (file)
@@ -40,7 +40,7 @@ class Method : public Process
 {
   public:
     Method(const char *name, ProcessFuncWrapper *func, bool _dynamic=false) :
-        Process(name, func, _dynamic)
+        Process(name, func, _dynamic, true)
     {}
 
     const char *kind() const override { return "sc_method_process"; }
@@ -56,7 +56,7 @@ class Thread : public Process
 {
   public:
     Thread(const char *name, ProcessFuncWrapper *func, bool _dynamic=false) :
-        Process(name, func, _dynamic), ctx(nullptr)
+        Process(name, func, _dynamic, false), ctx(nullptr)
     {}
 
     ~Thread() { delete ctx; }
index 0b385e9a0394be965e432f21c413a7431928d14e..120bbf9ae1751ee2c2c0b19a79389c05dfb2c3a4 100644 (file)
@@ -123,9 +123,6 @@ EmbeddedPyBind embed_("systemc", &systemc_pybind);
 sc_stop_mode _stop_mode = SC_STOP_FINISH_DELTA;
 sc_status _status = SC_ELABORATION;
 
-Tick _max_tick = MaxTick;
-sc_starvation_policy _starvation = SC_EXIT_ON_STARVATION;
-
 } // anonymous namespace
 
 int
@@ -143,28 +140,29 @@ sc_argv()
 void
 sc_start()
 {
-    _max_tick = MaxTick;
-    _starvation = SC_EXIT_ON_STARVATION;
-
-    // Switch back gem5.
-    Fiber::primaryFiber()->run();
+    Tick now = curEventQueue() ? curEventQueue()->getCurTick() : 0;
+    sc_start(sc_time::from_value(MaxTick - now), SC_EXIT_ON_STARVATION);
 }
 
 void
 sc_pause()
 {
-    warn("%s not implemented.\n", __PRETTY_FUNCTION__);
+    if (_status == SC_RUNNING)
+        ::sc_gem5::scheduler.schedulePause();
 }
 
 void
 sc_start(const sc_time &time, sc_starvation_policy p)
 {
+    _status = SC_RUNNING;
+
     Tick now = curEventQueue() ? curEventQueue()->getCurTick() : 0;
-    _max_tick = now + time.value();
-    _starvation = p;
+    ::sc_gem5::scheduler.start(now + time.value(), p == SC_RUN_TO_TIME);
 
-    // Switch back to gem5.
-    Fiber::primaryFiber()->run();
+    if (::sc_gem5::scheduler.paused())
+        _status = SC_PAUSED;
+    else if (::sc_gem5::scheduler.stopped())
+        _status = SC_STOPPED;
 }
 
 void
@@ -187,7 +185,15 @@ sc_get_stop_mode()
 void
 sc_stop()
 {
-    warn("%s not implemented.\n", __PRETTY_FUNCTION__);
+    if (_status == SC_STOPPED)
+        return;
+
+    if (sc_is_running()) {
+        bool finish_delta = (_stop_mode == SC_STOP_FINISH_DELTA);
+        ::sc_gem5::scheduler.scheduleStop(finish_delta);
+    } else {
+        //XXX Should stop if in one of the various elaboration callbacks.
+    }
 }
 
 const sc_time &
index 230ed0af2710ee48b5b3bb2c94b39d3fdabf5d32..e3d275bb1ddf618f1f1d90d6c528c2cd95c517bd 100644 (file)
@@ -37,7 +37,12 @@ namespace sc_gem5
 {
 
 Scheduler::Scheduler() :
-    eq(nullptr), readyEvent(this, false, EventBase::Default_Pri + 1),
+    eq(nullptr), _pendingCurr(0), _pendingFuture(0),
+    readyEvent(this, false, ReadyPriority),
+    pauseEvent(this, false, PausePriority),
+    stopEvent(this, false, StopPriority),
+    scMain(nullptr), _started(false), _paused(false), _stopped(false),
+    maxTickEvent(this, false, MaxTickPriority),
     _numCycles(0), _current(nullptr), initReady(false)
 {}
 
@@ -54,6 +59,9 @@ Scheduler::prepareForInit()
         p->ready();
     }
 
+    if (_started)
+        eq->schedule(&maxTickEvent, maxTick);
+
     initReady = true;
 }
 
@@ -98,9 +106,8 @@ Scheduler::yield()
         // Switch to whatever Fiber is supposed to run this process. All
         // Fibers which aren't running should be parked at this line.
         _current->fiber()->run();
-        // If the current process hasn't been started yet, start it. This
-        // should always be true for methods, but may not be true for threads.
-        if (_current && !_current->running())
+        // If the current process needs to be manually started, start it.
+        if (_current && _current->needsStart())
             _current->run();
     }
 }
@@ -164,6 +171,75 @@ Scheduler::update()
     }
 }
 
+void
+Scheduler::pause()
+{
+    _paused = true;
+    scMain->run();
+}
+
+void
+Scheduler::stop()
+{
+    _stopped = true;
+    scMain->run();
+}
+
+void
+Scheduler::start(Tick max_tick, bool run_to_time)
+{
+    // We should be running from sc_main. Keep track of that Fiber to return
+    // to later.
+    scMain = Fiber::currentFiber();
+
+    _started = true;
+    _paused = false;
+    _stopped = false;
+
+    maxTick = max_tick;
+
+    if (initReady)
+        eq->schedule(&maxTickEvent, maxTick);
+
+    // Return to gem5 to let it run events, etc.
+    Fiber::primaryFiber()->run();
+
+    if (pauseEvent.scheduled())
+        eq->deschedule(&pauseEvent);
+    if (stopEvent.scheduled())
+        eq->deschedule(&stopEvent);
+    if (maxTickEvent.scheduled())
+        eq->deschedule(&maxTickEvent);
+}
+
+void
+Scheduler::schedulePause()
+{
+    if (pauseEvent.scheduled())
+        return;
+
+    eq->schedule(&pauseEvent, eq->getCurTick());
+}
+
+void
+Scheduler::scheduleStop(bool finish_delta)
+{
+    if (stopEvent.scheduled())
+        return;
+
+    if (!finish_delta) {
+        // If we're not supposed to finish the delta cycle, flush the list
+        // of ready processes and scheduled updates.
+        Process *p;
+        while ((p = readyList.getNext()))
+            p->popListNode();
+        Channel *c;
+        while ((c = updateList.getNext()))
+            c->popListNode();
+    }
+    eq->schedule(&stopEvent, eq->getCurTick());
+}
+
 Scheduler scheduler;
 
 } // namespace sc_gem5
index aa8ec9aa4277868840b483da5547994a067b96a1..5db5556ae5fb29de028d9db9f47412b98f667dd9 100644 (file)
 
 #include <vector>
 
+#include "base/logging.hh"
 #include "sim/eventq.hh"
 #include "systemc/core/channel.hh"
 #include "systemc/core/list.hh"
 #include "systemc/core/process.hh"
 
+class Fiber;
+
 namespace sc_gem5
 {
 
@@ -105,6 +108,32 @@ typedef NodeList<Channel> ChannelList;
  * will all happen together since the readyEvent priority is lower,
  * potentially marking new processes as ready. Once these events finish, the
  * readyEvent may run, starting the next delta cycle.
+ *
+ * PAUSE/STOP
+ *
+ * To inject a pause from sc_pause which should happen after the current delta
+ * cycle's delta notification phase, an event is scheduled with a lower than
+ * normal priority, but higher than the readyEvent. That ensures that any
+ * delta notifications which are scheduled with normal priority will happen
+ * first, since those are part of the current delta cycle. Then the pause
+ * event will happen before the next readyEvent which would start the next
+ * delta cycle. All of these events are scheduled for the current time, and so
+ * would happen before any timed notifications went off.
+ *
+ * To inject a stop from sc_stop, the delta cycles should stop before even the
+ * delta notifications have happened, but after the evaluate and update phases.
+ * For that, a stop event with slightly higher than normal priority will be
+ * scheduled so that it happens before any of the delta notification events
+ * which are at normal priority.
+ *
+ * MAX RUN TIME
+ *
+ * When sc_start is called, it's possible to pass in a maximum time the
+ * simulation should run to, at which point sc_pause is implicitly called.
+ * That's implemented by scheduling an event at the max time with a priority
+ * which is lower than all the others so that it happens only if time would
+ * advance. When that event triggers, it calls the same function as the pause
+ * event.
  */
 
 class Scheduler
@@ -156,13 +185,44 @@ class Scheduler
     // Run scheduled channel updates.
     void update();
 
+    void setScMainFiber(Fiber *sc_main) { scMain = sc_main; }
+
+    void start(Tick max_tick, bool run_to_time);
+
+    void schedulePause();
+    void scheduleStop(bool finish_delta);
+
+    bool paused() { return _paused; }
+    bool stopped() { return _stopped; }
+
   private:
+    typedef const EventBase::Priority Priority;
+    static Priority DefaultPriority = EventBase::Default_Pri;
+
+    static Priority StopPriority = DefaultPriority - 1;
+    static Priority PausePriority = DefaultPriority + 1;
+    static Priority ReadyPriority = DefaultPriority + 2;
+    static Priority MaxTickPriority = DefaultPriority + 3;
+
     EventQueue *eq;
 
     void runReady();
     EventWrapper<Scheduler, &Scheduler::runReady> readyEvent;
     void scheduleReadyEvent();
 
+    void pause();
+    void stop();
+    EventWrapper<Scheduler, &Scheduler::pause> pauseEvent;
+    EventWrapper<Scheduler, &Scheduler::stop> stopEvent;
+    Fiber *scMain;
+
+    bool _started;
+    bool _paused;
+    bool _stopped;
+
+    Tick maxTick;
+    EventWrapper<Scheduler, &Scheduler::pause> maxTickEvent;
+
     uint64_t _numCycles;
 
     Process *_current;